Skip to content

Commit

Permalink
Winston Logger Adapter
Browse files Browse the repository at this point in the history
  • Loading branch information
ardatan committed Feb 10, 2025
1 parent bf41711 commit e8d806a
Show file tree
Hide file tree
Showing 5 changed files with 532 additions and 2 deletions.
5 changes: 5 additions & 0 deletions .changeset/wild-balloons-repair.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@graphql-hive/winston': major
---

Winston Adapter
61 changes: 61 additions & 0 deletions packages/winston/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
{
"name": "@graphql-hive/winston",
"version": "0.0.0",
"type": "module",
"repository": {
"type": "git",
"url": "git+https://github.com/graphql-hive/gateway.git",
"directory": "packages/winston"
},
"homepage": "https://the-guild.dev/graphql/hive/docs/gateway",
"author": {
"email": "contact@the-guild.dev",
"name": "The Guild",
"url": "https://the-guild.dev"
},
"license": "MIT",
"engines": {
"node": ">=18.0.0"
},
"main": "./dist/index.js",
"exports": {
".": {
"require": {
"types": "./dist/index.d.cts",
"default": "./dist/index.cjs"
},
"import": {
"types": "./dist/index.d.ts",
"default": "./dist/index.js"
}
},
"./package.json": "./package.json"
},
"types": "./dist/index.d.ts",
"files": [
"dist"
],
"scripts": {
"build": "pkgroll --clean-dist",
"prepack": "yarn build"
},
"peerDependencies": {
"graphql": "^15.9.0 || ^16.9.0",
"winston": "^3.17.0"
},
"peerDependenciesMeta": {
"@parcel/watcher": {
"optional": true
}
},
"dependencies": {
"@graphql-mesh/types": "^0.103.6",
"tslib": "^2.8.1"
},
"devDependencies": {
"graphql": "16.10.0",
"pkgroll": "2.8.2",
"winston": "^3.17.0"
},
"sideEffects": false
}
71 changes: 71 additions & 0 deletions packages/winston/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import type {
LazyLoggerMessage,
Logger as MeshLogger,
} from '@graphql-mesh/types';
import type { Logger as WinstonLogger } from 'winston';

function prepareArgs(lazyArgs: LazyLoggerMessage[]) {
const flattenedArgs = lazyArgs
.flatMap((lazyArg) => (typeof lazyArg === 'function' ? lazyArg() : lazyArg))
.flat(Infinity)
.map((arg) => {
if (typeof arg === 'string') {
try {
arg = JSON.parse(arg);
} catch (e) {
// Do nothing
}
}
return arg;
});
if (flattenedArgs.length === 1) {
return flattenedArgs[0];
}
return flattenedArgs;
}

class LoggerAdapter implements MeshLogger {
constructor(
private winstonLogger: WinstonLogger,
public names: string[] = [],
) {}
get name() {
return this.names.join(' - ');
}
log(...args: any[]) {
if (this.winstonLogger.isInfoEnabled()) {
this.winstonLogger.info(prepareArgs(args));
}
}
info(...args: any[]) {
if (this.winstonLogger.isInfoEnabled()) {
this.winstonLogger.info(prepareArgs(args));
}
}
warn(...args: any[]) {
if (this.winstonLogger.isWarnEnabled()) {
this.winstonLogger.warn(prepareArgs(args));
}
}
error(...args: any[]) {
if (this.winstonLogger.isErrorEnabled()) {
this.winstonLogger.error(prepareArgs(args));
}
}
debug(...lazyArgs: LazyLoggerMessage[]) {
if (this.winstonLogger.isDebugEnabled()) {
this.winstonLogger.debug(prepareArgs(lazyArgs));
}
}
child(name: string) {
const newName = [...new Set([...this.names, name])];
const childWinston = this.winstonLogger.child({ name: newName });
return new LoggerAdapter(childWinston, newName);
}
}

export function createLoggerFromWinston(
winstonLogger: WinstonLogger,
): MeshLogger {
return new LoggerAdapter(winstonLogger);
}
179 changes: 179 additions & 0 deletions packages/winston/tests/winston.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
import { Writable } from 'stream';
import { describe, expect, it } from 'vitest';
import * as winston from 'winston';
import { createLoggerFromWinston } from '../src';

describe('Winston', () => {
let log: string = '';
let lastCallback: () => void = () => {};
const stream = new Writable({
write(chunk, _encoding, callback) {
log = chunk.toString('utf-8');
lastCallback = callback;
},
});
const logLevels = ['error', 'warn', 'info', 'debug'] as const;
for (const level of logLevels) {
describe(`Level: ${level}`, () => {
it('basic', () => {
const logger = winston.createLogger({
level,
format: winston.format.json(),
transports: [
new winston.transports.Stream({
stream,
}),
],
});
const loggerAdapter = createLoggerFromWinston(logger);
const testData = [
'Hello',
['World'],
{ foo: 'bar' },
42,
true,
null,
undefined,
() => 'Expensive',
];
loggerAdapter[level](...testData);
lastCallback();
const logJson = JSON.parse(log);
expect(logJson).toEqual({
level,
message: [
'Hello',
'World',
{ foo: 'bar' },
42,
true,
null,
null,
'Expensive',
],
});
});
it('child', () => {
const logger = winston.createLogger({
level,
format: winston.format.json(),
transports: [
new winston.transports.Stream({
stream,
}),
],
});
const loggerAdapter = createLoggerFromWinston(logger);
const testData = [
'Hello',
['World'],
{ foo: 'bar' },
42,
true,
null,
undefined,
() => 'Expensive',
];
const childLogger = loggerAdapter.child('child');
childLogger[level](...testData);
lastCallback();
const logJson = JSON.parse(log);
expect(logJson).toEqual({
level,
message: [
'Hello',
'World',
{ foo: 'bar' },
42,
true,
null,
null,
'Expensive',
],
name: ['child'],
});
});
it('deduplicate names', () => {
const logger = winston.createLogger({
level,
format: winston.format.json(),
transports: [
new winston.transports.Stream({
stream,
}),
],
});
const loggerAdapter = createLoggerFromWinston(logger);
const testData = [
'Hello',
['World'],
{ foo: 'bar' },
42,
true,
null,
undefined,
() => 'Expensive',
];
const childLogger = loggerAdapter.child('child').child('child');
childLogger[level](...testData);
lastCallback();
const logJson = JSON.parse(log);
expect(logJson).toEqual({
level,
message: [
'Hello',
'World',
{ foo: 'bar' },
42,
true,
null,
null,
'Expensive',
],
name: ['child'],
});
});
it('nested', () => {
const logger = winston.createLogger({
level,
format: winston.format.json(),
transports: [
new winston.transports.Stream({
stream,
}),
],
});
const loggerAdapter = createLoggerFromWinston(logger);
const testData = [
'Hello',
['World'],
{ foo: 'bar' },
42,
true,
null,
undefined,
() => 'Expensive',
];
const childLogger = loggerAdapter.child('child');
const nestedLogger = childLogger.child('nested');
nestedLogger[level](...testData);
lastCallback();
const logJson = JSON.parse(log);
expect(logJson).toEqual({
level,
message: [
'Hello',
'World',
{ foo: 'bar' },
42,
true,
null,
null,
'Expensive',
],
name: ['child', 'nested'],
});
});
});
}
});
Loading

0 comments on commit e8d806a

Please sign in to comment.