Skip to content

Commit

Permalink
Add Winston transport for logging
Browse files Browse the repository at this point in the history
Implements a Winston transport for logging. The transport uses the
global client instance to log messages via the extension. A best
effort is made to preserve additional arguments added via splat,
formatters and child loggers.
  • Loading branch information
unflxw committed Jan 16, 2023
1 parent 6f8f4f8 commit 9bcd836
Show file tree
Hide file tree
Showing 5 changed files with 465 additions and 1 deletion.
6 changes: 6 additions & 0 deletions .changesets/add-winston-transport.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
bump: "patch"
type: "add"
---

Add Winston transport support for the logging feature.
330 changes: 330 additions & 0 deletions src/__tests__/winston_transport.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,330 @@
import { Client } from "../client"
import { WinstonTransport } from "../winston_transport"
import { Logger, createLogger, format, config } from "winston"

const transports = () => [
new WinstonTransport({
group: "groupname"
})
]

describe("BaseLogger", () => {
let client: Client
let logger: Logger

beforeAll(() => {
client = new Client({
active: true,
name: "TEST APP",
pushApiKey: "PUSH_API_KEY"
})
})

afterAll(() => {
client.stop()
})

beforeEach(() => {
logger = createLogger({ transports: transports() })

client.extension.log = jest.fn()
})

it("groups multiple arguments into message and attributes", () => {
logger.info("some data", { foo: 123 }, "and some more data", { bar: 456 })
expect(client.extension.log).toHaveBeenCalledWith(
"groupname",
3,
"some data and some more data",
{ foo: 123, bar: 456 }
)
})

it("carries arguments passed to child loggers", () => {
const childLogger = logger.child({ child: "foo" })
childLogger.info("child logger message", { argument: 123 })
expect(client.extension.log).toHaveBeenCalledWith(
"groupname",
3,
"child logger message",
{ child: "foo", argument: 123 }
)
})

it("ignores object keys with non-flat values", () => {
logger.info(
"no nested keys",
{ argument: 123, ignore: ["nested"] },
{ also: { ignore: "me" } }
)
expect(client.extension.log).toHaveBeenCalledWith(
"groupname",
3,
"no nested keys",
{ argument: 123 }
)
})

it("ignores arrays", () => {
logger.info("no arrays", ["foo", "bar"], ["baz"], { argument: 123 })
expect(client.extension.log).toHaveBeenCalledWith(
"groupname",
3,
"no arrays",
{ argument: 123 }
)
})

it("is not affected by the colorize formatter", () => {
const logger = createLogger({
format: format.colorize(),
transports: transports()
})

logger.info("no color for me", { argument: 123 })
expect(client.extension.log).toHaveBeenCalledWith(
"groupname",
3,
"no color for me",
{ argument: 123 }
)
})

it("is not affected by the timestamp formatter", () => {
const logger = createLogger({
format: format.timestamp(),
transports: transports()
})
logger.info("no timestamp for me", { argument: 123 })
expect(client.extension.log).toHaveBeenCalledWith(
"groupname",
3,
"no timestamp for me",
{ argument: 123 }
)
})

it("defaults to an info logger level", () => {
logger.info("info message")
logger.http("http message") // http < info, will be ignored

expect(client.extension.log).toHaveBeenCalledTimes(1)
expect(client.extension.log).toHaveBeenCalledWith(
"groupname",
3,
"info message",
{}
)
})

it("can set logger level by transport", () => {
const logger = createLogger({
transports: [
new WinstonTransport({
group: "groupname",
level: "silly"
})
]
})

logger.silly("silly message") // below default threshold

expect(client.extension.log).toHaveBeenCalledTimes(1)
expect(client.extension.log).toHaveBeenCalledWith(
"groupname",
1,
"silly message",
{}
)
})

it("silently ignores unknown log levels", () => {
const logger = createLogger({
levels: {
foobar: 1
},
level: "foobar",
transports: transports()
})
logger.log("foobar", "foobar message")
expect(client.extension.log).not.toHaveBeenCalled()
})

it("logs with all rust logger levels except log", () => {
const logger = createLogger({
levels: {
trace: 6,
debug: 5,
info: 4,
warn: 2,
error: 1
},
level: "trace",
transports: transports()
})

logger.log("trace", "trace message")
logger.log("debug", "debug message")
logger.log("info", "info message")
logger.log("warn", "warn message")
logger.log("error", "error message")

expect(client.extension.log).toHaveBeenCalledTimes(5)
expect(client.extension.log).toHaveBeenCalledWith(
"groupname",
1,
"trace message",
{}
)
expect(client.extension.log).toHaveBeenCalledWith(
"groupname",
2,
"debug message",
{}
)
expect(client.extension.log).toHaveBeenCalledWith(
"groupname",
3,
"info message",
{}
)
expect(client.extension.log).toHaveBeenCalledWith(
"groupname",
5,
"warn message",
{}
)
expect(client.extension.log).toHaveBeenCalledWith(
"groupname",
6,
"error message",
{}
)
})

it("logs with all npm logger levels", () => {
const logger = createLogger({
level: "silly",
transports: transports()
})

logger.silly("silly message")
logger.debug("debug message")
logger.verbose("verbose message")
logger.http("http message")
logger.info("info message")
logger.warn("warn message")
logger.error("error message")

expect(client.extension.log).toHaveBeenCalledTimes(7)
expect(client.extension.log).toHaveBeenCalledWith(
"groupname",
1,
"silly message",
{}
)
expect(client.extension.log).toHaveBeenCalledWith(
"groupname",
2,
"debug message",
{}
)
expect(client.extension.log).toHaveBeenCalledWith(
"groupname",
2,
"verbose message",
{}
)
expect(client.extension.log).toHaveBeenCalledWith(
"groupname",
3,
"http message",
{}
)
expect(client.extension.log).toHaveBeenCalledWith(
"groupname",
3,
"http message",
{}
)
expect(client.extension.log).toHaveBeenCalledWith(
"groupname",
5,
"warn message",
{}
)
expect(client.extension.log).toHaveBeenCalledWith(
"groupname",
6,
"error message",
{}
)
})

it("logs with all syslog logger levels", () => {
const logger = createLogger({
level: "debug",
levels: config.syslog.levels,
transports: transports()
})

logger.emerg("emerg message")
logger.alert("alert message")
logger.crit("crit message")
logger.error("error message")
logger.warning("warning message")
logger.notice("notice message")
logger.info("info message")
logger.debug("debug message")

expect(client.extension.log).toHaveBeenCalledTimes(8)
expect(client.extension.log).toHaveBeenCalledWith(
"groupname",
9,
"emerg message",
{}
)
expect(client.extension.log).toHaveBeenCalledWith(
"groupname",
8,
"alert message",
{}
)
expect(client.extension.log).toHaveBeenCalledWith(
"groupname",
7,
"crit message",
{}
)
expect(client.extension.log).toHaveBeenCalledWith(
"groupname",
6,
"error message",
{}
)
expect(client.extension.log).toHaveBeenCalledWith(
"groupname",
5,
"warning message",
{}
)
expect(client.extension.log).toHaveBeenCalledWith(
"groupname",
4,
"notice message",
{}
)
expect(client.extension.log).toHaveBeenCalledWith(
"groupname",
3,
"info message",
{}
)
expect(client.extension.log).toHaveBeenCalledWith(
"groupname",
2,
"debug message",
{}
)
})
})
2 changes: 2 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,6 @@ export { SpanProcessor } from "./span_processor"
export { RedisDbStatementSerializer } from "./instrumentation/redis/serializer"
export { RedisDbStatementSerializer as IORedisDbStatementSerializer } from "./instrumentation/redis/serializer"
export { expressErrorHandler } from "./instrumentation/express/error_handler"
export { LoggerLevel, LoggerAttributes } from "./logger"
export { WinstonTransport } from "./winston_transport"
export * from "./helpers"
2 changes: 1 addition & 1 deletion src/logger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ export type LoggerLevel = "trace" | "debug" | "info" | "log" | "warn" | "error"

export type LoggerAttributes = Record<string, string | number | boolean>

const LOGGER_LEVEL_SEVERITY: Record<LoggerLevel, number> = {
export const LOGGER_LEVEL_SEVERITY: Record<LoggerLevel, number> = {
trace: 1,
debug: 2,
info: 3,
Expand Down
Loading

0 comments on commit 9bcd836

Please sign in to comment.