UNPKG

4.27 kBJavaScriptView Raw
1import { relative } from "node:path";
2import { fileURLToPath } from "node:url";
3import { inspect } from "node:util";
4
5/**
6 * @param {NodeJS.WritableStream} stream
7 * @param {string} level
8 * @param {Date} timestamp
9 * @param {string} context
10 * @param {*} message
11 * @returns {void}
12 */
13export function loggerWritePretty(stream, level, timestamp, context, message) {
14 stream.write(`${loggerFormatPretty(level, timestamp, context, message)}\n`);
15}
16
17/**
18 * @param {NodeJS.WritableStream} stream
19 * @param {string} level
20 * @param {Date} timestamp
21 * @param {string} context
22 * @param {*} message
23 * @returns {void}
24 */
25export function loggerWriteGithubActions(
26 stream,
27 level,
28 timestamp,
29 context,
30 message,
31) {
32 if (level === "error") {
33 // file=app.js,line=10,col=15
34 const { relativePath, column, line } = loggerGetCaller();
35
36 // See https://github.com/actions/toolkit/issues/193#issuecomment-605394935 for the
37 // replace hack
38 stream.write(
39 `::error file=${relativePath},line=${line},col=${column}::${loggerFormatPretty(
40 undefined, // Always an error
41 timestamp,
42 context,
43 message,
44 )
45 .replace(/\n/g, "%0A")
46
47 // Removes ansi color codes from logs
48 // eslint-disable-next-line no-control-regex
49 .replace(/\u001b\[.*?m/g, "")}\n`,
50 );
51 } else {
52 loggerWritePretty(stream, level, timestamp, context, message);
53 }
54}
55
56/**
57 * @param {string|undefined} level
58 * @param {Date} timestamp
59 * @param {string|any} context
60 * @param {*} message
61 * @returns {string}
62 */
63export function loggerFormatPretty(level, timestamp, context, message) {
64 let prefix = level
65 ? `${loggerFormatDate(timestamp)} ${loggerFormatLevel(
66 level,
67 context?.type,
68 )} `
69 : "";
70
71 if (Object.keys(context).length > (context?.type ? 1 : 0)) {
72 prefix += `${loggerFormatMessagePretty(context)} `;
73 }
74
75 if (message) {
76 return `${prefix + loggerFormatMessagePretty(message)}`;
77 }
78
79 return prefix;
80}
81
82/**
83 * @param {*} value
84 * @returns {string}
85 */
86function loggerFormatMessagePretty(value) {
87 if (
88 typeof value === "boolean" ||
89 typeof value === "string" ||
90 typeof value === "number"
91 ) {
92 return String(value);
93 }
94
95 return inspect(value, {
96 colors: true,
97 depth: null,
98 });
99}
100
101/**
102 * @param {Date} date
103 * @returns {string}
104 */
105function loggerFormatDate(date) {
106 const h = date.getHours().toString(10).padStart(2, "0");
107 const m = date.getMinutes().toString(10).padStart(2, "0");
108 const s = date.getSeconds().toString(10).padStart(2, "0");
109 const ms = date.getMilliseconds().toString(10).padStart(3, "0");
110
111 return `${h}:${m}:${s}.${ms}`;
112}
113
114/**
115 * @param {string} level
116 * @param {string} type
117 * @returns {string}
118 */
119function loggerFormatLevel(level, type) {
120 const str =
121 typeof type === "string" && type.length > 0 ? `${level}[${type}]` : level;
122
123 return level === "error"
124 ? `\x1b[31m${str}\x1b[39m`
125 : `\x1b[34m${str}\x1b[39m`;
126}
127
128/**
129 * Get the caller of the error function, by parsing the stack. May fail
130 *
131 * @returns {{
132 * relativePath: string,
133 * line: number,
134 * column: number,
135 * }}
136 */
137function loggerGetCaller() {
138 const err = {};
139 Error.captureStackTrace(err);
140
141 const stackLines = err.stack.split("\n").slice(1);
142
143 let callerStackLine = stackLines[0].trim();
144 for (const line of stackLines) {
145 if (
146 line.includes("loggerGetCaller") ||
147 line.includes("loggerWriteGithubActions") ||
148 line.includes("Object.write") ||
149 line.includes("Object.error") ||
150 line.includes("Pino.")
151 ) {
152 continue;
153 }
154
155 callerStackLine = line.trim();
156 break;
157 }
158
159 const rawLocation =
160 callerStackLine.split(" ")[callerStackLine.includes(" async ") ? 3 : 2];
161
162 if (callerStackLine.length === 0 || (rawLocation?.length ?? 0) < 5) {
163 return {
164 relativePath: rawLocation,
165 line: 1,
166 column: 1,
167 };
168 }
169
170 const rawLocationParts = rawLocation
171 .substring(1, rawLocation.length - 1)
172 .split(":");
173
174 const rawFile = rawLocationParts
175 .splice(0, rawLocationParts.length - 2)
176 .join(":");
177
178 return {
179 relativePath: relative(process.cwd(), fileURLToPath(rawFile)),
180 line: rawLocationParts[0],
181 column: rawLocationParts[1],
182 };
183}