@devopsplaybook.io/otel-utils 1.8.0 → 1.9.0-beta.40.3ebb823

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,137 +0,0 @@
1
- import { SeverityNumber } from "@opentelemetry/api";
2
- import type { Span } from "@opentelemetry/api";
3
- import type { ConsoleLogMode } from "./models/ConfigOTelInterface";
4
- import { StandardLoggerInterface } from "./models/StandardLoggerInterface";
5
-
6
- export interface ModuleLoggerOptions {
7
- consoleMode?: ConsoleLogMode;
8
- }
9
-
10
- export class ModuleLogger {
11
- private module: string;
12
- private standardLogger: StandardLoggerInterface;
13
- private consoleMode: ConsoleLogMode;
14
-
15
- constructor(
16
- module: string,
17
- standardLogger: StandardLoggerInterface,
18
- options: ModuleLoggerOptions = {},
19
- ) {
20
- this.module = module;
21
- this.standardLogger = standardLogger;
22
- this.consoleMode = options.consoleMode ?? "always";
23
- }
24
-
25
- public info(message: string, context?: Span): void {
26
- this.display("info", message, SeverityNumber.INFO, null, context);
27
- }
28
-
29
- public warn(message: string, context?: Span): void {
30
- this.display("warn", message, SeverityNumber.WARN, null, context);
31
- }
32
-
33
- public error(message: string, error?: unknown, context?: Span): void {
34
- if (!context && isSpanLike(error)) {
35
- context = error;
36
- error = undefined;
37
- }
38
- this.display(
39
- "error",
40
- message,
41
- SeverityNumber.ERROR,
42
- normalizeError(error),
43
- context,
44
- );
45
- }
46
-
47
- private display(
48
- level: string,
49
- message: string,
50
- severityNumber = SeverityNumber.INFO,
51
- error?: Error | null,
52
- context?: Span,
53
- ): void {
54
- const attributes: Record<string, string> = {
55
- "log.type": "custom",
56
- "log.module": this.module,
57
- };
58
-
59
- if (error) {
60
- attributes["exception.type"] = error.name;
61
- attributes["exception.message"] = error.message;
62
- if (error.stack !== undefined) {
63
- attributes["exception.stacktrace"] = error.stack;
64
- }
65
- }
66
-
67
- if (context) {
68
- const spanCtx = context.spanContext();
69
- attributes["span.id"] = spanCtx.spanId;
70
- attributes["trace.id"] = spanCtx.traceId;
71
- }
72
-
73
- const logger = this.standardLogger.getLogger();
74
- const mirrorToConsole =
75
- this.consoleMode === "always" ||
76
- (this.consoleMode === "fallback" && !logger);
77
- if (mirrorToConsole) {
78
- writeToConsole(level, `[${level}] [${this.module}] ${message}`);
79
- }
80
- if (!logger) {
81
- return;
82
- }
83
- logger.emit({
84
- severityNumber,
85
- severityText: level,
86
- body: `[${this.module}] ${message}`,
87
- attributes,
88
- });
89
- }
90
- }
91
-
92
- function normalizeError(value: unknown): Error | null {
93
- if (value === undefined || value === null) {
94
- return null;
95
- }
96
- if (value instanceof Error) {
97
- return value;
98
- }
99
- return new Error(serializeUnknown(value));
100
- }
101
-
102
- function serializeUnknown(value: unknown): string {
103
- if (typeof value === "string") {
104
- return value;
105
- }
106
- try {
107
- const json = JSON.stringify(value);
108
- if (json !== undefined) {
109
- return json;
110
- }
111
- } catch {
112
- // Not serialisable; fall back to String below.
113
- }
114
- try {
115
- return String(value);
116
- } catch {
117
- return "Unknown error";
118
- }
119
- }
120
-
121
- function isSpanLike(value: unknown): value is Span {
122
- return (
123
- typeof value === "object" &&
124
- value !== null &&
125
- typeof (value as { spanContext?: unknown }).spanContext === "function"
126
- );
127
- }
128
-
129
- function writeToConsole(level: string, line: string): void {
130
- if (level === "error") {
131
- console.error(line);
132
- } else if (level === "warn") {
133
- console.warn(line);
134
- } else {
135
- console.log(line);
136
- }
137
- }
@@ -1,75 +0,0 @@
1
- import { registerShutdownHooks, shutdownAll } from "./ShutdownHooks";
2
-
3
- describe("shutdownAll", () => {
4
- it("shuts down every provider", async () => {
5
- const first = { shutdown: jest.fn().mockResolvedValue(undefined) };
6
- const second = { shutdown: jest.fn().mockResolvedValue(undefined) };
7
-
8
- await shutdownAll([first, second]);
9
-
10
- expect(first.shutdown).toHaveBeenCalledTimes(1);
11
- expect(second.shutdown).toHaveBeenCalledTimes(1);
12
- });
13
-
14
- it("resolves when a provider fails and reports it", async () => {
15
- const warnSpy = jest.spyOn(console, "warn").mockImplementation(() => {});
16
- const failure = new Error("boom");
17
- const failing = { shutdown: jest.fn().mockRejectedValue(failure) };
18
- const healthy = { shutdown: jest.fn().mockResolvedValue(undefined) };
19
-
20
- await expect(shutdownAll([failing, healthy])).resolves.toBeUndefined();
21
-
22
- expect(healthy.shutdown).toHaveBeenCalledTimes(1);
23
- expect(warnSpy).toHaveBeenCalledWith(
24
- "[otel-utils] shutdown failed",
25
- failure,
26
- );
27
- warnSpy.mockRestore();
28
- });
29
- });
30
-
31
- describe("registerShutdownHooks", () => {
32
- it("flushes the providers and exits once on SIGTERM/SIGINT", async () => {
33
- const exitSpy = jest
34
- .spyOn(process, "exit")
35
- .mockImplementation((() => undefined) as never);
36
- const provider = { shutdown: jest.fn().mockResolvedValue(undefined) };
37
- const sigtermListeners = process.listenerCount("SIGTERM");
38
- const sigintListeners = process.listenerCount("SIGINT");
39
-
40
- const cleanup = registerShutdownHooks([provider]);
41
- try {
42
- expect(process.listenerCount("SIGTERM")).toBe(sigtermListeners + 1);
43
- expect(process.listenerCount("SIGINT")).toBe(sigintListeners + 1);
44
-
45
- process.emit("SIGTERM");
46
- await new Promise((resolve) => setImmediate(resolve));
47
- expect(provider.shutdown).toHaveBeenCalledTimes(1);
48
- expect(exitSpy).toHaveBeenCalledWith(0);
49
-
50
- process.emit("SIGINT");
51
- await new Promise((resolve) => setImmediate(resolve));
52
- expect(provider.shutdown).toHaveBeenCalledTimes(1);
53
- expect(exitSpy).toHaveBeenCalledTimes(1);
54
- } finally {
55
- cleanup();
56
- exitSpy.mockRestore();
57
- }
58
-
59
- expect(process.listenerCount("SIGTERM")).toBe(sigtermListeners);
60
- expect(process.listenerCount("SIGINT")).toBe(sigintListeners);
61
- });
62
-
63
- it("the cleanup function removes both listeners without shutting down", () => {
64
- const provider = { shutdown: jest.fn().mockResolvedValue(undefined) };
65
- const sigtermListeners = process.listenerCount("SIGTERM");
66
- const sigintListeners = process.listenerCount("SIGINT");
67
-
68
- const cleanup = registerShutdownHooks([provider]);
69
- cleanup();
70
-
71
- expect(process.listenerCount("SIGTERM")).toBe(sigtermListeners);
72
- expect(process.listenerCount("SIGINT")).toBe(sigintListeners);
73
- expect(provider.shutdown).not.toHaveBeenCalled();
74
- });
75
- });
@@ -1,45 +0,0 @@
1
- export interface ShutdownableInterface {
2
- shutdown(): Promise<void>;
3
- }
4
-
5
- /**
6
- * Flushes and shuts down every provider, resolving even when one of them
7
- * fails (the failure is reported with `console.warn`).
8
- */
9
- export async function shutdownAll(
10
- providers: ReadonlyArray<ShutdownableInterface>,
11
- ): Promise<void> {
12
- await Promise.all(
13
- providers.map((provider) =>
14
- provider.shutdown().catch((error: unknown) => {
15
- console.warn("[otel-utils] shutdown failed", error);
16
- }),
17
- ),
18
- );
19
- }
20
-
21
- /**
22
- * Registers one-shot `SIGTERM`/`SIGINT` handlers that flush and shut down all
23
- * providers, then exit the process with code 0.
24
- *
25
- * Register once with every configured provider. The returned function removes
26
- * the handlers without shutting anything down.
27
- */
28
- export function registerShutdownHooks(
29
- providers: ReadonlyArray<ShutdownableInterface>,
30
- ): () => void {
31
- let shuttingDown = false;
32
- const onSignal = (): void => {
33
- if (shuttingDown) {
34
- return;
35
- }
36
- shuttingDown = true;
37
- void shutdownAll(providers).then(() => process.exit(0));
38
- };
39
- process.once("SIGTERM", onSignal);
40
- process.once("SIGINT", onSignal);
41
- return () => {
42
- process.removeListener("SIGTERM", onSignal);
43
- process.removeListener("SIGINT", onSignal);
44
- };
45
- }