Skip to content
Merged
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
5 changes: 3 additions & 2 deletions docs/appenders/file.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,13 +72,14 @@ When `all-the-logs.log` reaches 10Mb again, then all-the-logs.log.1.gz will be r
## Example with date rolling

```typescript
import {Logger} from "@tsed/logger-file";
import {Logger} from "@tsed/logger";
import "@tsed/logger-file";

export const logger = new Logger("Log Example");

logger.appenders.set("file", {
type: "file",
filename: `${__dirname}/../logs/myfile.log`,
filename: `${import.meta.dirname}/../logs/myfile.log`,
pattern: ".yyyy-MM-dd"
});
```
8 changes: 5 additions & 3 deletions docs/appenders/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,21 @@ Appenders serialise log events to some form of output. They can write to files,

```typescript
import {Logger} from "@tsed/logger";
import "@tsed/logger-file";
import "@tsed/logger-std";

const logger = new Logger("loggerName");

logger.appenders
.set({
.set("stdout", {
type: "stdout",
levels: ["debug", "info", "trace"]
})
.set({
.set("stderr", {
type: "stderr",
levels: ["error", "fatal", "warn"]
})
.set({
.set("file", {
type: "file",
filename: "logfile.log"
});
Expand Down
22 changes: 19 additions & 3 deletions docs/introduction/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,18 @@ otherTopics: true
## Installation

```bash
npm install -g typescript
npm install @tsed/logger
npm install source-map-support
```

`@tsed/logger` v8 is ESM only and ships the console appender and the colored layout. Other appenders and layouts are
installed and imported on demand:

```bash
npm install @tsed/logger-std @tsed/logger-file @tsed/logger-pattern-layout
```

See the [migration guide](/introduction/migrate-to-v8) when you come from v7.

## Quick start

Minimalist version:
Expand All @@ -35,6 +42,10 @@ Will be produce the following log output:

```typescript
import {Logger} from "@tsed/logger";
import "@tsed/logger/layouts/JsonLayout.js"; // registers the "json" layout
import "@tsed/logger-file"; // registers the "file" appender
import "@tsed/logger-pattern-layout"; // registers the "pattern" layout
import "@tsed/logger-std"; // registers the "stdout" and "stderr" appenders

const logger = new Logger("loggerName");
logger.appenders
Expand All @@ -52,14 +63,19 @@ logger.appenders
})
.set("all-log-file", {
type: "file",
filename: `${__dirname}/app.log`,
filename: `${import.meta.dirname}/app.log`,
layout: {
type: "json",
separator: ","
}
});
```

::: warning
An appender or a layout whose package is not imported is unknown to the logger: it prints a warning and falls back to the
console appender or the colored layout.
:::

## Shutdown

Shutdown return a Promise that will be resolved when logger has closed all appenders and finished writing log events.
Expand Down
2 changes: 1 addition & 1 deletion docs/introduction/migrate-to-v8.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ Then import and use it:
import {StdoutAppender, StdErrAppender} from "@tsed/logger";

// v8 (new)
import {StdoutAppender, StdErrAppender} from "@tsed/logger-std";
import {StdoutAppender, StderrAppender} from "@tsed/logger-std";
```

#### Using PatternLayout
Expand Down
16 changes: 5 additions & 11 deletions docs/layouts/custom.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,10 @@ You can add your own layouts with @@Layout()@@ before pushing a configure to you
```typescript [Decorator]
// customLayout.ts
import {BaseLayout, LogEvent, Layout} from "@tsed/logger";
import {formatLogData} from "ts-log-debug/lib/utils/inpectUtils";

@Layout({name: "customJson"})
export class JsonLayout extends BaseLayout {
transform(loggingEvent: LogEvent, timezoneOffset?): string {
transform(loggingEvent: LogEvent, timezoneOffset?: number): string {
const log = {
startTime: loggingEvent.startTime,
categoryName: loggingEvent.categoryName,
Expand All @@ -20,20 +19,17 @@ export class JsonLayout extends BaseLayout {
context: loggingEvent.context
};

log.data = log.data.map((data) => formatLogData([data]));

return JSON.stringify(log) + (this.config["separator"] || "");
}
}
```

```typescript [Functional API]
// customLayout.ts
import {BaseLayout, LogEvent, Layout, layout} from "@tsed/logger";
import {formatLogData} from "ts-log-debug/lib/utils/inpectUtils";
import {BaseLayout, LogEvent, layout} from "@tsed/logger";

export class JsonLayout extends BaseLayout {
transform(loggingEvent: LogEvent, timezoneOffset?): string {
transform(loggingEvent: LogEvent, timezoneOffset?: number): string {
const log = {
startTime: loggingEvent.startTime,
categoryName: loggingEvent.categoryName,
Expand All @@ -42,8 +38,6 @@ export class JsonLayout extends BaseLayout {
context: loggingEvent.context
};

log.data = log.data.map((data) => formatLogData([data]));

return JSON.stringify(log) + (this.config["separator"] || "");
}
}
Expand All @@ -57,7 +51,7 @@ This layout can be use like this:

```typescript
import {Logger} from "@tsed/logger";
import "./customLayout.ts";
import "./customLayout.js";

const logger = new Logger("loggerName");

Expand All @@ -76,5 +70,5 @@ This example outputs the following:
```bash
{"startTime":"2017-06-05T22:23:08.479Z","categoryName":"json-test","data":["this is just a test"],"level":"INFO","context":{}},
{"startTime":"2017-06-05T22:23:08.483Z","categoryName":"json-test","data":["of a custom appender"],"level":"ERROR","context":{}},
{"startTime":"2017-06-05T22:23:08.483Z","categoryName":"json-test","data":["that outputs json"],"level""WARN","context":{}},
{"startTime":"2017-06-05T22:23:08.483Z","categoryName":"json-test","data":["that outputs json"],"level":"WARN","context":{}},
```
4 changes: 3 additions & 1 deletion packages/logstash-http/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,9 @@
},
"scripts": {
"build": "yarn run build:ts",
"build:ts": "tsc --build tsconfig.esm.json"
"build:ts": "tsc --build tsconfig.esm.json",
"test": "vitest run",
"test:ci": "vitest run --coverage.thresholds.autoUpdate=true"
Comment on lines +42 to +43

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Declare the test runner dependencies

When this workspace is installed or tested in isolation (for example through a focused workspace install), the new test script cannot find vitest, and its configuration also imports the undeclared @tsed/vitest/presets; the full monorepo currently masks both omissions through dependencies hoisted from other workspaces. Add vitest and @tsed/vitest to this package's devDependencies, and make the same correction in packages/logstash-udp/package.json.

Useful? React with 👍 / 👎.

},
"dependencies": {
"axios": ">=1.20.0",
Expand Down
156 changes: 156 additions & 0 deletions packages/logstash-http/src/LogStashHttpAppender.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
import {AppendersRegistry, Logger} from "@tsed/logger";
import axios from "axios";
import axiosRetry from "axios-retry";
import {vi} from "vitest";

import {LogStashHttpAppender} from "./LogStashHttpAppender.js";

vi.mock("axios");
vi.mock("axios-retry");

function createLogger(options: Record<string, unknown> = {}) {
const client = vi.fn().mockResolvedValue({status: 200, data: {errors: false}});

vi.mocked(axios.create).mockReturnValue(client as never);

const logger = new Logger("test");

logger.appenders.set("logstash", {
type: "logstash-http",
options: {
url: "http://localhost:9200/_bulk",
application: "logstash-test",
logType: "application",
logChannel: "node",
...options
}
});

return {logger, client};
}

function getLines(client: ReturnType<typeof vi.fn>, call = 0) {
return client.mock.calls[call][0].data
.split("\n")
.filter(Boolean)
.map((line: string) => JSON.parse(line));
}

describe("LogStashHttpAppender", () => {
beforeEach(() => {
vi.clearAllMocks();
});

it("should be registered under the logstash-http name", () => {
expect(AppendersRegistry.get("logstash-http")!.provide).toBe(LogStashHttpAppender);
});

it("should create the HTTP client from the options", () => {
createLogger({
auth: {username: "user", password: "pass"},
timeout: 1000,
params: {pipeline: "main"},
headers: {"x-api-key": "key"},
retryOptions: {retries: 5}
});

expect(axios.create).toHaveBeenCalledWith(
expect.objectContaining({
baseURL: "http://localhost:9200/_bulk",
auth: {username: "user", password: "pass"},
timeout: 1000,
params: {pipeline: "main"},
headers: {"x-api-key": "key"}
})
);
expect(axiosRetry).toHaveBeenCalledWith(expect.any(Function), expect.objectContaining({retries: 5}));
});

it("should post each log as a bulk request when no buffer is configured", () => {
const {logger, client} = createLogger();

logger.info({event: "STARTED"});

expect(client).toHaveBeenCalledWith(
expect.objectContaining({
method: "POST",
headers: {"Content-Type": "application/x-ndjson"}
})
);

const [action, document] = getLines(client);

expect(action.index).toMatchObject({
_index: "logstash-test",
_type: "application",
_id: expect.any(String)
});
expect(document).toMatchObject({
event: "STARTED",
level_name: "info",
channel: "node",
datetime: expect.any(String)
});
});

it("should resolve the index from a function", () => {
const {logger, client} = createLogger({application: () => "dynamic-index"});

logger.info("hello");

expect(getLines(client)[0].index._index).toEqual("dynamic-index");
});

it("should wait for bufferMax logs before posting", () => {
const {logger, client} = createLogger({bufferMax: 2});

logger.info("first");

expect(client).not.toHaveBeenCalled();

logger.info("second");

expect(client).toHaveBeenCalledTimes(1);
expect(getLines(client)).toHaveLength(4);
});

it("should flush the buffer on shutdown", async () => {
const {logger, client} = createLogger({bufferMax: 10});

logger.info("first");

expect(client).not.toHaveBeenCalled();

await logger.shutdown();

expect(client).toHaveBeenCalledTimes(1);
});

it("should report a response error", async () => {
const {logger, client} = createLogger({bufferMax: 10});
const error = vi.spyOn(console, "error").mockReturnValue(undefined);

client.mockRejectedValue({response: {status: 500, data: {reason: "failed"}}});

logger.info("first");
await logger.shutdown();

expect(error).toHaveBeenCalledWith(expect.stringContaining('error posting to http://localhost:9200/_bulk: 500 - {"reason":"failed"}'));

error.mockRestore();
});

it("should report a network error", async () => {
const {logger, client} = createLogger({bufferMax: 10});
const error = vi.spyOn(console, "error").mockReturnValue(undefined);

client.mockRejectedValue(new Error("ECONNREFUSED"));

logger.info("first");
await logger.shutdown();

expect(error).toHaveBeenCalledWith("Ts.ED Logger.logstash-http Appender error: ECONNREFUSED");

error.mockRestore();
});
});
19 changes: 19 additions & 0 deletions packages/logstash-http/vitest.config.mts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// @ts-ignore
import {presets} from "@tsed/vitest/presets";
import {defineConfig} from "vitest/config";

export default defineConfig({
...presets,
test: {
...presets.test,
coverage: {
...presets.test.coverage,
thresholds: {
statements: 0,
branches: 0,
functions: 0,
lines: 0
}
}
}
});
4 changes: 3 additions & 1 deletion packages/logstash-udp/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,9 @@
},
"scripts": {
"build": "yarn run build:ts",
"build:ts": "tsc --build tsconfig.esm.json"
"build:ts": "tsc --build tsconfig.esm.json",
"test": "vitest run",
"test:ci": "vitest run --coverage.thresholds.autoUpdate=true"
},
"dependencies": {
"tslib": "2.8.1"
Expand Down
Loading
Loading