Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add a new logger package #146

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -105,3 +105,4 @@ dist
.vscode
.envrc
.turbo
.rollup.cache/
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
},
"type": "module",
"devDependencies": {
"@edge-runtime/vm": "^3.2.0",
"@rollup/plugin-replace": "^5.0.2",
"@rollup/plugin-typescript": "^11.1.2",
"@types/node": "^20.4.1",
Expand All @@ -55,6 +56,7 @@
"jsdom": "^22.1.0",
"prettier": "^2.6.2",
"rollup": "^3.29.4",
"runtime": "^0.14.1",
"ts-node": "^10.8.1",
"tslib": "^2.6.2",
"turbo": "^1.10.1",
Expand Down
14 changes: 14 additions & 0 deletions packages/logger/.npmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
.github
.DS_Store
examples/
lib/
node_modules/
tests/
test/
tsconfig.json
coverage/
.turbo/
.rollup.cache/
tsconfig.tsbuildinfo
rollup.config.js
rollup.config.cjs.js
8 changes: 8 additions & 0 deletions packages/logger/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Changelog

All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]
9 changes: 9 additions & 0 deletions packages/logger/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
## Javascript logger for Axiom

## Quickstart

Install using `npm install`:

```shell
npm install @axiomhq/js @axiomhq/logger
```
58 changes: 58 additions & 0 deletions packages/logger/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
{
"name": "@axiomhq/logger",
"description": "The official Axiom javascript logger",
"version": "1.0.0",
"author": "Axiom, Inc.",
"license": "MIT",
"contributors": [
"Islam Shehata <[email protected]>",
"Arne Bahlo <[email protected]>"
],
"engines": {
"node": ">=18"
},
"type": "module",
"types": "dist/esm/types/index.d.ts",
"module": "dist/esm/index.js",
"main": "dist/cjs/index.cjs",
"scripts": {
"build": "rollup -c rollup.config.js",
"build:cjs": "rollup -c rollup.config.cjs.js",
"format": "eslint '*/**/*.{js,ts}' --quiet --fix",
"lint": "eslint '*/**/*.{js,ts}'",
"prepublish": "pnpm run build && pnpm run build:cjs",
"test": "vitest run test/unit/* --coverage"
},
"repository": {
"type": "git",
"url": "git+https://github.com/axiomhq/axiom-js.git"
},
"keywords": [
"axiom",
"api",
"rest",
"client",
"axiom-js",
"axiom sdk",
"axiom js",
"logging",
"logger",
"axiom logger"
],
"bugs": {
"url": "https://github.com/axiomhq/axiom-js/issues"
},
"homepage": "https://github.com/axiomhq/axiom-js/blob/main/packages/logger/README.md",
"dependencies": {
"@axiomhq/runtime": "workspace:*"
},
"peerDependencies": {
"@axiomhq/js": "workspace:*"
},
"exports": {
"types": "./dist/esm/types/index.d.ts",
"import": "./dist/esm/index.js",
"require": "./dist/cjs/index.cjs",
"default": "./dist/esm/index.js"
}
}
16 changes: 16 additions & 0 deletions packages/logger/rollup.config.cjs.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import typescript from '@rollup/plugin-typescript';

export default [
{
input: 'src/index.ts',
output: {
dir: 'dist/cjs',
format: 'cjs',
exports: 'named',
sourcemap: true,
preserveModules: true,
entryFileNames: '[name].cjs',
},
plugins: [typescript({ outDir: 'dist/cjs', declarationDir: 'dist/cjs/types' })],
},
];
21 changes: 21 additions & 0 deletions packages/logger/rollup.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import typescript from '@rollup/plugin-typescript';
import replace from '@rollup/plugin-replace';

export default [
{
input: 'src/index.ts',
output: {
dir: 'dist/esm',
format: 'esm',
exports: 'named',
sourcemap: true,
preserveModules: true,
},
plugins: [
typescript({ outDir: 'dist/esm', declarationDir: 'dist/esm/types' }),
replace({
AXIOM_VERSION: process.env.npm_package_version,
}),
],
},
];
7 changes: 7 additions & 0 deletions packages/logger/src/event.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export interface LogEvent {
level: string;
message: string;
fields: any;
_time: string;
[key: string]: any;
}
25 changes: 25 additions & 0 deletions packages/logger/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { LOG_LEVEL, LogLevel } from './levels';
import { Logger } from './logger';
import { AxiomTransport } from './transports/axiom';
import { ConsoleTransport } from './transports/console';

export { Logger, LoggerConfig } from './logger'
export { LogEvent } from './event'
export { LogLevel } from './levels'

export function getDefaultLogger(): Logger {
const level = LogLevel[LOG_LEVEL as keyof typeof LogLevel];
const env = process.env.NODE_ENV
const transport = env === 'production' ? new AxiomTransport({
token: process.env.AXIOM_TOKEN || '',
axiomUrl: process.env.AXIOM_URL || 'https://api.axiom.co',
dataset: process.env.AXIOM_DATASET || '',
}) : new ConsoleTransport();

return new Logger({
logLevel: level,
autoFlush: false,
transformers: [],
transport: transport,
});
}
10 changes: 10 additions & 0 deletions packages/logger/src/levels.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
export const LOG_LEVEL = process.env.AXIOM_LOG_LEVEL || 'debug';

export enum LogLevel {
debug = 0,
info = 1,
warn = 2,
error = 3,
fatal = 4,
off = 100,
}
98 changes: 98 additions & 0 deletions packages/logger/src/logger.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import { LogEvent } from './event';
import { LOG_LEVEL, LogLevel } from './levels';
import { Transport } from './transport';
import { Transformer } from './transformer';

export type LoggerConfig = {
logLevel: LogLevel;
autoFlush: boolean;
transport: Transport;
transformers: Transformer[];
args?: { [key: string]: any };
};

export class Logger {
children: Logger[] = [];

constructor(public config: LoggerConfig) {
if (this.config.logLevel == undefined || this.config.logLevel < 0) {
this.config.logLevel = LogLevel[LOG_LEVEL as keyof typeof LogLevel];
}
}

debug = (message: string, args: { [key: string]: any } = {}) => {
this._log(LogLevel.debug, message, args);
};
info = (message: string, args: { [key: string]: any } = {}) => {
this._log(LogLevel.info, message, args);
};
warn = (message: string, args: { [key: string]: any } = {}) => {
this._log(LogLevel.warn, message, args);
};
error = (message: string, args: { [key: string]: any } = {}) => {
this._log(LogLevel.error, message, args);
};
fatal = (message: string, args: { [key: string]: any } = {}) => {
this._log(LogLevel.fatal, message, args);
};

with = (args: { [key: string]: any }) => {
const config = { ...this.config, args: { ...this.config.args, ...args } };
const child = new Logger(config);
this.children.push(child);
return child;
};

private _log = (level: LogLevel, message: string, args: { [key: string]: any } = {}) => {
if (level < this.config.logLevel) {
return;
}
let logEvent: LogEvent = {
level: LogLevel[level].toString(),
message,
_time: new Date(Date.now()).toISOString(),
fields: this.config.args || {},
};

// check if passed args is an object, if its not an object, add it to fields.args
if (args instanceof Error) {
logEvent.fields = { ...logEvent.fields, message: args.message, stack: args.stack, name: args.name };
} else if (typeof args === 'object' && args !== null && Object.keys(args).length > 0) {
const parsedArgs = JSON.parse(JSON.stringify(args, jsonFriendlyErrorReplacer));
logEvent.fields = { ...logEvent.fields, ...parsedArgs };
} else if (args && args.length) {
logEvent.fields = { ...logEvent.fields, args: args };
}

// loop over transformers and apply them to the logEvent
for (let t of this.config.transformers) {
logEvent = t.transform(logEvent);
}

this.config.transport.log(logEvent);
if (this.config.autoFlush) {
this.flush();
}
};

flush = async () => {
await Promise.all([this.config.transport.flush(), ...this.children.map((c) => c.flush())]);
};
}


function jsonFriendlyErrorReplacer(key: string, value: any) {
if (value instanceof Error) {
return {
dasfmi marked this conversation as resolved.
Show resolved Hide resolved
// Pull all enumerable properties, supporting properties on custom Errors
...value,
// Explicitly pull Error's non-enumerable properties
name: value.name,
message: value.message,
stack: value.stack,
cause: value.cause,
};
}

return value;
}
5 changes: 5 additions & 0 deletions packages/logger/src/transformer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { LogEvent } from "./event";

export interface Transformer {
transform(event: LogEvent): LogEvent;
}
6 changes: 6 additions & 0 deletions packages/logger/src/transport.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { LogEvent } from "./event";

export interface Transport {
log(event: LogEvent): void;
flush(): Promise<any>;
}
24 changes: 24 additions & 0 deletions packages/logger/src/transports/axiom.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { Axiom } from '@axiomhq/js';
import { Transport } from '../transport';

export class AxiomTransport implements Transport {
private client: Axiom;

constructor(public credentials: AxiomCredentials) {
this.client = new Axiom({ token: credentials.token, url: credentials.axiomUrl });
}

log(event: any) {
this.client.ingest(this.credentials.dataset, event)
}

async flush() {
return this.client.flush();
}
}

export interface AxiomCredentials {
token: string;
axiomUrl: string;
dataset: string;
}
Loading
Loading