@holz/console-backend 0.0.0
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.
- package/CHANGELOG.md +7 -0
- package/dist/holz-console-backend.js +60 -0
- package/dist/holz-console-backend.js.map +1 -0
- package/dist/holz-console-backend.umd.cjs +2 -0
- package/dist/holz-console-backend.umd.cjs.map +1 -0
- package/package.json +48 -0
- package/src/__tests__/__snapshots__/console-backend.test.ts.snap +65 -0
- package/src/__tests__/console-backend.test.ts +127 -0
- package/src/console-backend.ts +88 -0
- package/src/index.ts +1 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
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).
|
|
6
|
+
|
|
7
|
+
## Unreleased
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
var n = /* @__PURE__ */ ((o) => (o.Error = "error", o.Warn = "warn", o.Info = "info", o.Debug = "debug", o))(n || {});
|
|
2
|
+
class i {
|
|
3
|
+
constructor(e = {}) {
|
|
4
|
+
this.console = e.console ?? console;
|
|
5
|
+
}
|
|
6
|
+
processLog(e) {
|
|
7
|
+
const r = [
|
|
8
|
+
{
|
|
9
|
+
include: !0,
|
|
10
|
+
command: "%c%s",
|
|
11
|
+
content: u[e.level],
|
|
12
|
+
style: `font-weight: bold; color: ${l[e.level]}`
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
include: !0,
|
|
16
|
+
command: "%c%s",
|
|
17
|
+
content: e.message,
|
|
18
|
+
style: `font-weight: normal; color: ${e.level === n.Debug ? "darkgray" : "unset"}`
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
include: Object.keys(e.context).length > 0,
|
|
22
|
+
command: "%c%o",
|
|
23
|
+
// Chrome hides object content with `%O`.
|
|
24
|
+
content: e.context,
|
|
25
|
+
style: ""
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
include: e.origin.length > 0,
|
|
29
|
+
command: "%c%s",
|
|
30
|
+
content: e.origin.join(":"),
|
|
31
|
+
style: "color: gray"
|
|
32
|
+
}
|
|
33
|
+
].filter((t) => t.include), c = r.map((t) => t.command).join(" "), s = r.flatMap((t) => [
|
|
34
|
+
t.style,
|
|
35
|
+
t.content
|
|
36
|
+
]), a = d[e.level];
|
|
37
|
+
this.console[a](c, ...s);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
const l = {
|
|
41
|
+
[n.Debug]: "darkgray",
|
|
42
|
+
[n.Info]: "darkturquoise",
|
|
43
|
+
[n.Warn]: "unset",
|
|
44
|
+
[n.Error]: "unset"
|
|
45
|
+
}, u = {
|
|
46
|
+
[n.Debug]: "DEBUG",
|
|
47
|
+
[n.Info]: "INFO ",
|
|
48
|
+
[n.Warn]: "WARN ",
|
|
49
|
+
[n.Error]: "ERROR"
|
|
50
|
+
}, d = {
|
|
51
|
+
[n.Debug]: "log",
|
|
52
|
+
[n.Info]: "log",
|
|
53
|
+
[n.Warn]: "warn",
|
|
54
|
+
[n.Error]: "error"
|
|
55
|
+
};
|
|
56
|
+
export {
|
|
57
|
+
i as ConsoleBackend,
|
|
58
|
+
i as default
|
|
59
|
+
};
|
|
60
|
+
//# sourceMappingURL=holz-console-backend.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"holz-console-backend.js","sources":["../../holz-core/dist/holz-core.js","../src/console-backend.ts"],"sourcesContent":["var t = /* @__PURE__ */ ((s) => (s.Error = \"error\", s.Warn = \"warn\", s.Info = \"info\", s.Debug = \"debug\", s))(t || {});\nclass e {\n constructor(r, o) {\n this.processor = r, this.origin = o;\n }\n static create(r) {\n return new e(r, []);\n }\n /** Extend the logger to attach a class or module name to logs. */\n namespace(r) {\n return new e(this.processor, this.origin.concat(r));\n }\n /** Log a frequent and verbose progress update. */\n debug(r, o) {\n this.forwardLog(t.Debug, r, o);\n }\n /** Log a high-level progress update. */\n info(r, o) {\n this.forwardLog(t.Info, r, o);\n }\n /** Log something concerning. */\n warn(r, o) {\n this.forwardLog(t.Warn, r, o);\n }\n /** Log a critical failure. */\n error(r, o) {\n this.forwardLog(t.Error, r, o);\n }\n forwardLog(r, o, c = {}) {\n this.processor.processLog({\n message: o,\n level: r,\n origin: this.origin,\n context: c\n });\n }\n}\nconst a = e.create;\nclass i {\n constructor(r) {\n this.processors = r;\n }\n processLog(r) {\n this.processors.forEach((o) => {\n o.processLog(r);\n });\n }\n}\nfunction h(s) {\n return new i(s);\n}\nclass n {\n constructor(r, o) {\n this.predicate = r, this.processor = o;\n }\n processLog(r) {\n this.predicate(r) && this.processor.processLog(r);\n }\n}\nfunction g(s, r) {\n return new n(s, r);\n}\nexport {\n t as LogLevel,\n h as combine,\n a as createLogger,\n g as filter\n};\n//# sourceMappingURL=holz-core.js.map\n","import type { Log, LogProcessor } from '@holz/core';\nimport { LogLevel } from '@holz/core';\n\n/**\n * A backend that pretty-prints logs to a browser console, or any\n * remote-attached console.\n */\nexport default class ConsoleBackend implements LogProcessor {\n private console: MinimalConsole;\n\n constructor(options: Options = {}) {\n this.console = options.console ?? console;\n }\n\n processLog(log: Log) {\n const segments = [\n {\n include: true,\n command: '%c%s',\n content: LOG_LEVEL_TITLE[log.level],\n style: `font-weight: bold; color: ${LOG_LEVEL_STYLE[log.level]}`,\n },\n {\n include: true,\n command: '%c%s',\n content: log.message,\n style: `font-weight: normal; color: ${\n log.level === LogLevel.Debug ? 'darkgray' : 'unset'\n }`,\n },\n {\n include: Object.keys(log.context).length > 0,\n command: '%c%o', // Chrome hides object content with `%O`.\n content: log.context,\n style: '',\n },\n {\n include: log.origin.length > 0,\n command: '%c%s',\n content: log.origin.join(':'),\n style: 'color: gray',\n },\n ].filter((segment) => segment.include);\n\n const command = segments.map((segment) => segment.command).join(' ');\n const values = segments.flatMap((segment) => [\n segment.style,\n segment.content,\n ]);\n\n // Errors should always use stderr.\n const channel = LOG_METHOD[log.level];\n\n this.console[channel](command, ...values);\n }\n}\n\n// Before changing these, verify contrast on dark/light themes.\nconst LOG_LEVEL_STYLE: Record<LogLevel, string> = {\n [LogLevel.Debug]: 'darkgray',\n [LogLevel.Info]: 'darkturquoise',\n [LogLevel.Warn]: 'unset',\n [LogLevel.Error]: 'unset',\n};\n\nconst LOG_LEVEL_TITLE: Record<LogLevel, string> = {\n [LogLevel.Debug]: 'DEBUG',\n [LogLevel.Info]: 'INFO ',\n [LogLevel.Warn]: 'WARN ',\n [LogLevel.Error]: 'ERROR',\n};\n\nconst LOG_METHOD: Record<LogLevel, keyof MinimalConsole> = {\n [LogLevel.Debug]: 'log',\n [LogLevel.Info]: 'log',\n [LogLevel.Warn]: 'warn',\n [LogLevel.Error]: 'error',\n};\n\ninterface Options {\n console?: MinimalConsole;\n}\n\n/**\n * A subset of the Console interface. Must support printf-style interpolation.\n * @see https://console.spec.whatwg.org/#formatting-specifiers\n */\nexport type MinimalConsole = Pick<Console, 'log' | 'warn' | 'error'>;\n"],"names":["t","s","ConsoleBackend","options","log","segments","LOG_LEVEL_TITLE","LOG_LEVEL_STYLE","LogLevel","segment","command","values","channel","LOG_METHOD"],"mappings":"AAAA,IAAIA,IAAqB,kBAACC,OAAOA,EAAE,QAAQ,SAASA,EAAE,OAAO,QAAQA,EAAE,OAAO,QAAQA,EAAE,QAAQ,SAASA,IAAID,KAAK,EAAE;ACOpH,MAAqBE,EAAuC;AAAA,EAG1D,YAAYC,IAAmB,IAAI;AAC5B,SAAA,UAAUA,EAAQ,WAAW;AAAA,EACpC;AAAA,EAEA,WAAWC,GAAU;AACnB,UAAMC,IAAW;AAAA,MACf;AAAA,QACE,SAAS;AAAA,QACT,SAAS;AAAA,QACT,SAASC,EAAgBF,EAAI,KAAK;AAAA,QAClC,OAAO,6BAA6BG,EAAgBH,EAAI,KAAK;AAAA,MAC/D;AAAA,MACA;AAAA,QACE,SAAS;AAAA,QACT,SAAS;AAAA,QACT,SAASA,EAAI;AAAA,QACb,OAAO,+BACLA,EAAI,UAAUI,EAAS,QAAQ,aAAa;AAAA,MAEhD;AAAA,MACA;AAAA,QACE,SAAS,OAAO,KAAKJ,EAAI,OAAO,EAAE,SAAS;AAAA,QAC3C,SAAS;AAAA;AAAA,QACT,SAASA,EAAI;AAAA,QACb,OAAO;AAAA,MACT;AAAA,MACA;AAAA,QACE,SAASA,EAAI,OAAO,SAAS;AAAA,QAC7B,SAAS;AAAA,QACT,SAASA,EAAI,OAAO,KAAK,GAAG;AAAA,QAC5B,OAAO;AAAA,MACT;AAAA,IACA,EAAA,OAAO,CAACK,MAAYA,EAAQ,OAAO,GAE/BC,IAAUL,EAAS,IAAI,CAACI,MAAYA,EAAQ,OAAO,EAAE,KAAK,GAAG,GAC7DE,IAASN,EAAS,QAAQ,CAACI,MAAY;AAAA,MAC3CA,EAAQ;AAAA,MACRA,EAAQ;AAAA,IAAA,CACT,GAGKG,IAAUC,EAAWT,EAAI,KAAK;AAEpC,SAAK,QAAQQ,CAAO,EAAEF,GAAS,GAAGC,CAAM;AAAA,EAC1C;AACF;AAGA,MAAMJ,IAA4C;AAAA,EAChD,CAACC,EAAS,KAAK,GAAG;AAAA,EAClB,CAACA,EAAS,IAAI,GAAG;AAAA,EACjB,CAACA,EAAS,IAAI,GAAG;AAAA,EACjB,CAACA,EAAS,KAAK,GAAG;AACpB,GAEMF,IAA4C;AAAA,EAChD,CAACE,EAAS,KAAK,GAAG;AAAA,EAClB,CAACA,EAAS,IAAI,GAAG;AAAA,EACjB,CAACA,EAAS,IAAI,GAAG;AAAA,EACjB,CAACA,EAAS,KAAK,GAAG;AACpB,GAEMK,IAAqD;AAAA,EACzD,CAACL,EAAS,KAAK,GAAG;AAAA,EAClB,CAACA,EAAS,IAAI,GAAG;AAAA,EACjB,CAACA,EAAS,IAAI,GAAG;AAAA,EACjB,CAACA,EAAS,KAAK,GAAG;AACpB;"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
(function(o,e){typeof exports=="object"&&typeof module<"u"?e(exports):typeof define=="function"&&define.amd?define(["exports"],e):(o=typeof globalThis<"u"?globalThis:o||self,e(o["holz-console-backend"]={}))})(this,function(o){"use strict";var e=(t=>(t.Error="error",t.Warn="warn",t.Info="info",t.Debug="debug",t))(e||{});class r{constructor(n={}){this.console=n.console??console}processLog(n){const s=[{include:!0,command:"%c%s",content:a[n.level],style:`font-weight: bold; color: ${l[n.level]}`},{include:!0,command:"%c%s",content:n.message,style:`font-weight: normal; color: ${n.level===e.Debug?"darkgray":"unset"}`},{include:Object.keys(n.context).length>0,command:"%c%o",content:n.context,style:""},{include:n.origin.length>0,command:"%c%s",content:n.origin.join(":"),style:"color: gray"}].filter(c=>c.include),u=s.map(c=>c.command).join(" "),d=s.flatMap(c=>[c.style,c.content]),f=i[n.level];this.console[f](u,...d)}}const l={[e.Debug]:"darkgray",[e.Info]:"darkturquoise",[e.Warn]:"unset",[e.Error]:"unset"},a={[e.Debug]:"DEBUG",[e.Info]:"INFO ",[e.Warn]:"WARN ",[e.Error]:"ERROR"},i={[e.Debug]:"log",[e.Info]:"log",[e.Warn]:"warn",[e.Error]:"error"};o.ConsoleBackend=r,o.default=r,Object.defineProperties(o,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}})});
|
|
2
|
+
//# sourceMappingURL=holz-console-backend.umd.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"holz-console-backend.umd.cjs","sources":["../../holz-core/dist/holz-core.js","../src/console-backend.ts"],"sourcesContent":["var t = /* @__PURE__ */ ((s) => (s.Error = \"error\", s.Warn = \"warn\", s.Info = \"info\", s.Debug = \"debug\", s))(t || {});\nclass e {\n constructor(r, o) {\n this.processor = r, this.origin = o;\n }\n static create(r) {\n return new e(r, []);\n }\n /** Extend the logger to attach a class or module name to logs. */\n namespace(r) {\n return new e(this.processor, this.origin.concat(r));\n }\n /** Log a frequent and verbose progress update. */\n debug(r, o) {\n this.forwardLog(t.Debug, r, o);\n }\n /** Log a high-level progress update. */\n info(r, o) {\n this.forwardLog(t.Info, r, o);\n }\n /** Log something concerning. */\n warn(r, o) {\n this.forwardLog(t.Warn, r, o);\n }\n /** Log a critical failure. */\n error(r, o) {\n this.forwardLog(t.Error, r, o);\n }\n forwardLog(r, o, c = {}) {\n this.processor.processLog({\n message: o,\n level: r,\n origin: this.origin,\n context: c\n });\n }\n}\nconst a = e.create;\nclass i {\n constructor(r) {\n this.processors = r;\n }\n processLog(r) {\n this.processors.forEach((o) => {\n o.processLog(r);\n });\n }\n}\nfunction h(s) {\n return new i(s);\n}\nclass n {\n constructor(r, o) {\n this.predicate = r, this.processor = o;\n }\n processLog(r) {\n this.predicate(r) && this.processor.processLog(r);\n }\n}\nfunction g(s, r) {\n return new n(s, r);\n}\nexport {\n t as LogLevel,\n h as combine,\n a as createLogger,\n g as filter\n};\n//# sourceMappingURL=holz-core.js.map\n","import type { Log, LogProcessor } from '@holz/core';\nimport { LogLevel } from '@holz/core';\n\n/**\n * A backend that pretty-prints logs to a browser console, or any\n * remote-attached console.\n */\nexport default class ConsoleBackend implements LogProcessor {\n private console: MinimalConsole;\n\n constructor(options: Options = {}) {\n this.console = options.console ?? console;\n }\n\n processLog(log: Log) {\n const segments = [\n {\n include: true,\n command: '%c%s',\n content: LOG_LEVEL_TITLE[log.level],\n style: `font-weight: bold; color: ${LOG_LEVEL_STYLE[log.level]}`,\n },\n {\n include: true,\n command: '%c%s',\n content: log.message,\n style: `font-weight: normal; color: ${\n log.level === LogLevel.Debug ? 'darkgray' : 'unset'\n }`,\n },\n {\n include: Object.keys(log.context).length > 0,\n command: '%c%o', // Chrome hides object content with `%O`.\n content: log.context,\n style: '',\n },\n {\n include: log.origin.length > 0,\n command: '%c%s',\n content: log.origin.join(':'),\n style: 'color: gray',\n },\n ].filter((segment) => segment.include);\n\n const command = segments.map((segment) => segment.command).join(' ');\n const values = segments.flatMap((segment) => [\n segment.style,\n segment.content,\n ]);\n\n // Errors should always use stderr.\n const channel = LOG_METHOD[log.level];\n\n this.console[channel](command, ...values);\n }\n}\n\n// Before changing these, verify contrast on dark/light themes.\nconst LOG_LEVEL_STYLE: Record<LogLevel, string> = {\n [LogLevel.Debug]: 'darkgray',\n [LogLevel.Info]: 'darkturquoise',\n [LogLevel.Warn]: 'unset',\n [LogLevel.Error]: 'unset',\n};\n\nconst LOG_LEVEL_TITLE: Record<LogLevel, string> = {\n [LogLevel.Debug]: 'DEBUG',\n [LogLevel.Info]: 'INFO ',\n [LogLevel.Warn]: 'WARN ',\n [LogLevel.Error]: 'ERROR',\n};\n\nconst LOG_METHOD: Record<LogLevel, keyof MinimalConsole> = {\n [LogLevel.Debug]: 'log',\n [LogLevel.Info]: 'log',\n [LogLevel.Warn]: 'warn',\n [LogLevel.Error]: 'error',\n};\n\ninterface Options {\n console?: MinimalConsole;\n}\n\n/**\n * A subset of the Console interface. Must support printf-style interpolation.\n * @see https://console.spec.whatwg.org/#formatting-specifiers\n */\nexport type MinimalConsole = Pick<Console, 'log' | 'warn' | 'error'>;\n"],"names":["t","s","ConsoleBackend","options","log","segments","LOG_LEVEL_TITLE","LOG_LEVEL_STYLE","LogLevel","segment","command","values","channel","LOG_METHOD"],"mappings":"+OAAA,IAAIA,GAAsBC,IAAOA,EAAE,MAAQ,QAASA,EAAE,KAAO,OAAQA,EAAE,KAAO,OAAQA,EAAE,MAAQ,QAASA,IAAID,GAAK,EAAE,ECOpH,MAAqBE,CAAuC,CAG1D,YAAYC,EAAmB,GAAI,CAC5B,KAAA,QAAUA,EAAQ,SAAW,OACpC,CAEA,WAAWC,EAAU,CACnB,MAAMC,EAAW,CACf,CACE,QAAS,GACT,QAAS,OACT,QAASC,EAAgBF,EAAI,KAAK,EAClC,MAAO,6BAA6BG,EAAgBH,EAAI,KAAK,GAC/D,EACA,CACE,QAAS,GACT,QAAS,OACT,QAASA,EAAI,QACb,MAAO,+BACLA,EAAI,QAAUI,EAAS,MAAQ,WAAa,SAEhD,EACA,CACE,QAAS,OAAO,KAAKJ,EAAI,OAAO,EAAE,OAAS,EAC3C,QAAS,OACT,QAASA,EAAI,QACb,MAAO,EACT,EACA,CACE,QAASA,EAAI,OAAO,OAAS,EAC7B,QAAS,OACT,QAASA,EAAI,OAAO,KAAK,GAAG,EAC5B,MAAO,aACT,CACA,EAAA,OAAQK,GAAYA,EAAQ,OAAO,EAE/BC,EAAUL,EAAS,IAAKI,GAAYA,EAAQ,OAAO,EAAE,KAAK,GAAG,EAC7DE,EAASN,EAAS,QAASI,GAAY,CAC3CA,EAAQ,MACRA,EAAQ,OAAA,CACT,EAGKG,EAAUC,EAAWT,EAAI,KAAK,EAEpC,KAAK,QAAQQ,CAAO,EAAEF,EAAS,GAAGC,CAAM,CAC1C,CACF,CAGA,MAAMJ,EAA4C,CAChD,CAACC,EAAS,KAAK,EAAG,WAClB,CAACA,EAAS,IAAI,EAAG,gBACjB,CAACA,EAAS,IAAI,EAAG,QACjB,CAACA,EAAS,KAAK,EAAG,OACpB,EAEMF,EAA4C,CAChD,CAACE,EAAS,KAAK,EAAG,QAClB,CAACA,EAAS,IAAI,EAAG,QACjB,CAACA,EAAS,IAAI,EAAG,QACjB,CAACA,EAAS,KAAK,EAAG,OACpB,EAEMK,EAAqD,CACzD,CAACL,EAAS,KAAK,EAAG,MAClB,CAACA,EAAS,IAAI,EAAG,MACjB,CAACA,EAAS,IAAI,EAAG,OACjB,CAACA,EAAS,KAAK,EAAG,OACpB"}
|
package/package.json
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@holz/console-backend",
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"description": "A console backend for Holz",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/holz-console-backend.umd.cjs",
|
|
7
|
+
"module": "./dist/holz-console-backend.js",
|
|
8
|
+
"types": "./src/index.ts",
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "https://github.com/PsychoLlama/holz",
|
|
12
|
+
"directory": "packages/holz-console-backend"
|
|
13
|
+
},
|
|
14
|
+
"exports": {
|
|
15
|
+
".": {
|
|
16
|
+
"require": "./dist/holz-console-backend.umd.cjs",
|
|
17
|
+
"import": "./dist/holz-console-backend.js"
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
"publishConfig": {
|
|
21
|
+
"access": "public"
|
|
22
|
+
},
|
|
23
|
+
"author": "Jesse Gibson",
|
|
24
|
+
"license": "MIT",
|
|
25
|
+
"sideEffects": false,
|
|
26
|
+
"files": [
|
|
27
|
+
"dist",
|
|
28
|
+
"src"
|
|
29
|
+
],
|
|
30
|
+
"keywords": [
|
|
31
|
+
"holz-backend",
|
|
32
|
+
"console",
|
|
33
|
+
"browser",
|
|
34
|
+
"debugger"
|
|
35
|
+
],
|
|
36
|
+
"scripts": {
|
|
37
|
+
"prepare": "vite build --sourcemap",
|
|
38
|
+
"test:unit": "vitest --color --passWithNoTests",
|
|
39
|
+
"test:coverage": "run test:unit --coverage"
|
|
40
|
+
},
|
|
41
|
+
"devDependencies": {
|
|
42
|
+
"@holz/core": "0.0.0",
|
|
43
|
+
"@types/node": "^18.14.0",
|
|
44
|
+
"@vitest/coverage-c8": "0.28.5",
|
|
45
|
+
"vite": "^4.0.0",
|
|
46
|
+
"vitest": "^0.28.5"
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
// Vitest Snapshot v1
|
|
2
|
+
|
|
3
|
+
exports[`Console backend > avoids changing debug messages 1`] = `
|
|
4
|
+
[
|
|
5
|
+
"%c%s %c%s %c%o %c%s",
|
|
6
|
+
"font-weight: bold; color: darkgray",
|
|
7
|
+
"DEBUG",
|
|
8
|
+
"font-weight: normal; color: darkgray",
|
|
9
|
+
"just spam",
|
|
10
|
+
"",
|
|
11
|
+
{
|
|
12
|
+
"id": 1234,
|
|
13
|
+
},
|
|
14
|
+
"color: gray",
|
|
15
|
+
"ns-1:ns-2",
|
|
16
|
+
]
|
|
17
|
+
`;
|
|
18
|
+
|
|
19
|
+
exports[`Console backend > avoids changing error messages 1`] = `
|
|
20
|
+
[
|
|
21
|
+
"%c%s %c%s %c%o %c%s",
|
|
22
|
+
"font-weight: bold; color: unset",
|
|
23
|
+
"ERROR",
|
|
24
|
+
"font-weight: normal; color: unset",
|
|
25
|
+
"some error message",
|
|
26
|
+
"",
|
|
27
|
+
{
|
|
28
|
+
"id": 1234,
|
|
29
|
+
},
|
|
30
|
+
"color: gray",
|
|
31
|
+
"ns-1:ns-2",
|
|
32
|
+
]
|
|
33
|
+
`;
|
|
34
|
+
|
|
35
|
+
exports[`Console backend > avoids changing info messages 1`] = `
|
|
36
|
+
[
|
|
37
|
+
"%c%s %c%s %c%o %c%s",
|
|
38
|
+
"font-weight: bold; color: darkturquoise",
|
|
39
|
+
"INFO ",
|
|
40
|
+
"font-weight: normal; color: unset",
|
|
41
|
+
"a little info",
|
|
42
|
+
"",
|
|
43
|
+
{
|
|
44
|
+
"id": 1234,
|
|
45
|
+
},
|
|
46
|
+
"color: gray",
|
|
47
|
+
"ns-1:ns-2",
|
|
48
|
+
]
|
|
49
|
+
`;
|
|
50
|
+
|
|
51
|
+
exports[`Console backend > avoids changing warn messages 1`] = `
|
|
52
|
+
[
|
|
53
|
+
"%c%s %c%s %c%o %c%s",
|
|
54
|
+
"font-weight: bold; color: unset",
|
|
55
|
+
"WARN ",
|
|
56
|
+
"font-weight: normal; color: unset",
|
|
57
|
+
"a warning",
|
|
58
|
+
"",
|
|
59
|
+
{
|
|
60
|
+
"id": 1234,
|
|
61
|
+
},
|
|
62
|
+
"color: gray",
|
|
63
|
+
"ns-1:ns-2",
|
|
64
|
+
]
|
|
65
|
+
`;
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { format } from 'util';
|
|
2
|
+
import { createLogger } from '@holz/core';
|
|
3
|
+
import type { MinimalConsole } from '../console-backend';
|
|
4
|
+
import ConsoleBackend from '../console-backend';
|
|
5
|
+
|
|
6
|
+
class MockConsole implements MinimalConsole {
|
|
7
|
+
log = vi.fn((...strings: Array<unknown>) => {
|
|
8
|
+
this.stdout(format(...strings));
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
warn = vi.fn((...strings: Array<unknown>) => {
|
|
12
|
+
this.stdout(format(...strings));
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
error = vi.fn((...strings: Array<unknown>) => {
|
|
16
|
+
this.stderr(format(...strings));
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
stdout = vi.fn();
|
|
20
|
+
stderr = vi.fn();
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
describe('Console backend', () => {
|
|
24
|
+
it('prints messages to the console', () => {
|
|
25
|
+
const output = new MockConsole();
|
|
26
|
+
const backend = new ConsoleBackend({ console: output });
|
|
27
|
+
|
|
28
|
+
const logger = createLogger(backend);
|
|
29
|
+
logger.info('hello world');
|
|
30
|
+
|
|
31
|
+
expect(output.stdout).toHaveBeenCalledWith(
|
|
32
|
+
expect.stringContaining('hello world')
|
|
33
|
+
);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('includes the log level', () => {
|
|
37
|
+
const output = new MockConsole();
|
|
38
|
+
const backend = new ConsoleBackend({ console: output });
|
|
39
|
+
|
|
40
|
+
const logger = createLogger(backend);
|
|
41
|
+
logger.debug('shout');
|
|
42
|
+
logger.info('normal');
|
|
43
|
+
logger.warn('hmmmm');
|
|
44
|
+
logger.error('oh no');
|
|
45
|
+
|
|
46
|
+
expect(output.stdout).toHaveBeenCalledWith(
|
|
47
|
+
expect.stringContaining('DEBUG')
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
expect(output.stdout).toHaveBeenCalledWith(expect.stringContaining('INFO'));
|
|
51
|
+
expect(output.stdout).toHaveBeenCalledWith(expect.stringContaining('WARN'));
|
|
52
|
+
expect(output.stderr).toHaveBeenCalledWith(
|
|
53
|
+
expect.stringContaining('ERROR')
|
|
54
|
+
);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('includes the log namespace', () => {
|
|
58
|
+
const output = new MockConsole();
|
|
59
|
+
const backend = new ConsoleBackend({ console: output });
|
|
60
|
+
const logger = createLogger(backend)
|
|
61
|
+
.namespace('my-lib')
|
|
62
|
+
.namespace('MyClass');
|
|
63
|
+
|
|
64
|
+
logger.debug('initialized');
|
|
65
|
+
|
|
66
|
+
expect(output.stdout).toHaveBeenCalledWith(
|
|
67
|
+
expect.stringContaining('my-lib:MyClass')
|
|
68
|
+
);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('includes the log context', () => {
|
|
72
|
+
const output = new MockConsole();
|
|
73
|
+
const backend = new ConsoleBackend({ console: output });
|
|
74
|
+
const logger = createLogger(backend);
|
|
75
|
+
|
|
76
|
+
logger.info('creating session', { sessionId: 3109 });
|
|
77
|
+
|
|
78
|
+
// Hard to test without replicating the implementation.
|
|
79
|
+
expect(output.stdout).toHaveBeenCalledWith(
|
|
80
|
+
expect.stringContaining('sessionId')
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
expect(output.stdout).toHaveBeenCalledWith(expect.stringContaining('3109'));
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it('does not include the log context if it is empty', () => {
|
|
87
|
+
const output = new MockConsole();
|
|
88
|
+
const backend = new ConsoleBackend({ console: output });
|
|
89
|
+
const logger = createLogger(backend);
|
|
90
|
+
|
|
91
|
+
logger.warn('activating death ray', {});
|
|
92
|
+
|
|
93
|
+
// Hard to test without replicating the implementation.
|
|
94
|
+
expect(output.stdout).not.toHaveBeenCalledWith(
|
|
95
|
+
expect.stringContaining('{')
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
expect(output.stdout).not.toHaveBeenCalledWith(
|
|
99
|
+
expect.stringContaining('}')
|
|
100
|
+
);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
// Snapshot the exact output of each log level. This mostly prevents
|
|
104
|
+
// regressions while doing innocent refactors.
|
|
105
|
+
it.each([
|
|
106
|
+
['debug' as const, 'just spam', ['ns-1', 'ns-2'], 'log' as const],
|
|
107
|
+
['info' as const, 'a little info', ['ns-1', 'ns-2'], 'log' as const],
|
|
108
|
+
['warn' as const, 'a warning', ['ns-1', 'ns-2'], 'warn' as const],
|
|
109
|
+
[
|
|
110
|
+
'error' as const,
|
|
111
|
+
'some error message',
|
|
112
|
+
['ns-1', 'ns-2'],
|
|
113
|
+
'error' as const,
|
|
114
|
+
],
|
|
115
|
+
])('avoids changing %s messages', (method, message, namespace, pipe) => {
|
|
116
|
+
const output = new MockConsole();
|
|
117
|
+
const backend = new ConsoleBackend({ console: output });
|
|
118
|
+
const logger = namespace.reduce(
|
|
119
|
+
(logger, ns) => logger.namespace(ns),
|
|
120
|
+
createLogger(backend)
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
logger[method](message, { id: 1234 });
|
|
124
|
+
|
|
125
|
+
expect(output[pipe].mock.calls[0]).toMatchSnapshot();
|
|
126
|
+
});
|
|
127
|
+
});
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import type { Log, LogProcessor } from '@holz/core';
|
|
2
|
+
import { LogLevel } from '@holz/core';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* A backend that pretty-prints logs to a browser console, or any
|
|
6
|
+
* remote-attached console.
|
|
7
|
+
*/
|
|
8
|
+
export default class ConsoleBackend implements LogProcessor {
|
|
9
|
+
private console: MinimalConsole;
|
|
10
|
+
|
|
11
|
+
constructor(options: Options = {}) {
|
|
12
|
+
this.console = options.console ?? console;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
processLog(log: Log) {
|
|
16
|
+
const segments = [
|
|
17
|
+
{
|
|
18
|
+
include: true,
|
|
19
|
+
command: '%c%s',
|
|
20
|
+
content: LOG_LEVEL_TITLE[log.level],
|
|
21
|
+
style: `font-weight: bold; color: ${LOG_LEVEL_STYLE[log.level]}`,
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
include: true,
|
|
25
|
+
command: '%c%s',
|
|
26
|
+
content: log.message,
|
|
27
|
+
style: `font-weight: normal; color: ${
|
|
28
|
+
log.level === LogLevel.Debug ? 'darkgray' : 'unset'
|
|
29
|
+
}`,
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
include: Object.keys(log.context).length > 0,
|
|
33
|
+
command: '%c%o', // Chrome hides object content with `%O`.
|
|
34
|
+
content: log.context,
|
|
35
|
+
style: '',
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
include: log.origin.length > 0,
|
|
39
|
+
command: '%c%s',
|
|
40
|
+
content: log.origin.join(':'),
|
|
41
|
+
style: 'color: gray',
|
|
42
|
+
},
|
|
43
|
+
].filter((segment) => segment.include);
|
|
44
|
+
|
|
45
|
+
const command = segments.map((segment) => segment.command).join(' ');
|
|
46
|
+
const values = segments.flatMap((segment) => [
|
|
47
|
+
segment.style,
|
|
48
|
+
segment.content,
|
|
49
|
+
]);
|
|
50
|
+
|
|
51
|
+
// Errors should always use stderr.
|
|
52
|
+
const channel = LOG_METHOD[log.level];
|
|
53
|
+
|
|
54
|
+
this.console[channel](command, ...values);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Before changing these, verify contrast on dark/light themes.
|
|
59
|
+
const LOG_LEVEL_STYLE: Record<LogLevel, string> = {
|
|
60
|
+
[LogLevel.Debug]: 'darkgray',
|
|
61
|
+
[LogLevel.Info]: 'darkturquoise',
|
|
62
|
+
[LogLevel.Warn]: 'unset',
|
|
63
|
+
[LogLevel.Error]: 'unset',
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const LOG_LEVEL_TITLE: Record<LogLevel, string> = {
|
|
67
|
+
[LogLevel.Debug]: 'DEBUG',
|
|
68
|
+
[LogLevel.Info]: 'INFO ',
|
|
69
|
+
[LogLevel.Warn]: 'WARN ',
|
|
70
|
+
[LogLevel.Error]: 'ERROR',
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
const LOG_METHOD: Record<LogLevel, keyof MinimalConsole> = {
|
|
74
|
+
[LogLevel.Debug]: 'log',
|
|
75
|
+
[LogLevel.Info]: 'log',
|
|
76
|
+
[LogLevel.Warn]: 'warn',
|
|
77
|
+
[LogLevel.Error]: 'error',
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
interface Options {
|
|
81
|
+
console?: MinimalConsole;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* A subset of the Console interface. Must support printf-style interpolation.
|
|
86
|
+
* @see https://console.spec.whatwg.org/#formatting-specifiers
|
|
87
|
+
*/
|
|
88
|
+
export type MinimalConsole = Pick<Console, 'log' | 'warn' | 'error'>;
|
package/src/index.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default, default as ConsoleBackend } from './console-backend';
|