![]() Server : Apache/2 System : Linux server-15-235-50-60 5.15.0-164-generic #174-Ubuntu SMP Fri Nov 14 20:25:16 UTC 2025 x86_64 User : gositeme ( 1004) PHP Version : 8.2.29 Disable Function : exec,system,passthru,shell_exec,proc_close,proc_open,dl,popen,show_source,posix_kill,posix_mkfifo,posix_getpwuid,posix_setpgid,posix_setsid,posix_setuid,posix_setgid,posix_seteuid,posix_setegid,posix_uname Directory : /home/gositeme/.local/lib/code-server-4.102.2/node_modules/@coder/logger/src/ |
/**
* Numeric log level. A higher number is higher severity.
*/
export enum Level {
Trace,
Debug,
Info,
Warn,
Error,
}
/**
* A message field.
*/
export class Field<T> {
public constructor(public readonly identifier: string, public readonly value: T) {}
public toJSON(): { identifier: string; value: T } {
return {
identifier: this.identifier,
value: this.value,
}
}
}
/**
* A time field.
*/
export class Time {
public constructor(public readonly expected: number, public readonly ms: number) {}
}
/**
* A generic argument to log alongside a message.
*/
export type Argument = any // eslint-disable-line @typescript-eslint/no-explicit-any
/**
* `undefined` is allowed to make it easier to conditionally display a field.
* For example: `error && field("error", error)`
*/
export type FieldArray = Array<Field<Argument> | undefined>
/**
* Functions can be used to remove the need to perform operations when the
* logging level won't output the result anyway.
*/
export type LogCallback = () => [string, ...FieldArray]
/**
* Log how long something took. Call this before doing the thing then pass it
* into the logger after finished to log the time it took.
*/
export const time = (expected: number): Time => {
return new Time(expected, Date.now())
}
/**
* A field to show with the message.
*/
export const field = <T>(name: string, value: T): Field<T> => {
return new Field(name, value)
}
/**
* Represents a log message, its fields, level, and color.
*/
export type Message = {
message: string | LogCallback
fields?: FieldArray
level: Level
tagColor: string
}
/**
* An extra function to call with a message.
*/
export type Extender = (msg: Message & { section?: string }) => void
/**
* Represents a message formatted for use with something like `console.log`.
*/
export type MessageFormat = {
/**
* For example, something like `[%s] %s`.
*/
format: ""
/**
* The arguments that get applied to the format string.
*/
args: string[]
/**
* The fields to add under the message.
*/
fields: Field<Argument>[]
}
export function doLog(level: Level, ...args: Argument): void {
switch (level) {
case Level.Trace:
return console.trace(...args)
case Level.Debug:
return console.debug(...args)
case Level.Info:
return console.info(...args)
case Level.Warn:
return console.warn(...args)
case Level.Error:
return console.error(...args)
}
}
/**
* Format and build a *single* log entry at a time.
*/
export abstract class Formatter {
private message: MessageFormat
private readonly minimumTagWidth = 5
/**
* formatType is used for the strings returned from style() and reset().
*/
public constructor(
private readonly formatType: string = "%s",
private readonly colors: boolean = true,
) {
this.message = this.newMessage()
}
/**
* Return a new/empty message.
*/
private newMessage(): MessageFormat {
return {
format: "",
args: [],
fields: [],
}
}
/**
* Add a tag.
*/
public tag(name: string, color: string): void {
for (let i = name.length; i < this.minimumTagWidth; ++i) {
name += " "
}
this.push(name + " ", color)
}
/**
* Add a field or an argument. Arguments will display inline in the order they
* were pushed. Fields will display differently based on the formatter. Fields
* cannot have custom colors.
*/
public push(fields: Field<Argument>[]): void
public push(arg: Argument, color?: string, weight?: string): void
public push(arg: Argument | Field<Argument>[], color?: string, weight?: string): void {
if (Array.isArray(arg) && arg.every((a) => a instanceof Field)) {
return void this.message.fields.push(...arg)
}
if (this.colors && (color || weight)) {
this.message.format += `${this.formatType}${this.getType(arg)}${this.formatType}`
this.message.args.push(this.style(color, weight), arg, this.reset())
} else {
this.message.format += `${this.getType(arg)}`
this.message.args.push(arg)
}
}
/**
* Write everything out and reset state.
*/
public write(level: Level): void {
const message = this.flush()
this.doWrite(level, message)
}
/**
* Return current values and reset state.
*/
protected flush(): MessageFormat {
const message = this.message
this.message = this.newMessage()
return message
}
/**
* Return a string that applies the specified color and weight.
*/
protected abstract style(color?: string, weight?: string): string
/**
* Return a string that resets all styles.
*/
protected abstract reset(): string
/**
* Write everything out.
*/
protected abstract doWrite(level: Level, message: MessageFormat): void
/**
* Get the format string for the value type.
*/
private getType(arg: Argument): string {
switch (typeof arg) {
case "object":
return "%o"
case "number":
return "%d"
default:
return "%s"
}
}
}
/**
* Display logs in the browser using CSS in the output. Fields are displayed on
* individual lines within a group.
*/
export class BrowserFormatter extends Formatter {
public constructor() {
super("%c")
}
protected style(color?: string, weight?: string): string {
return (color ? `color: ${color};` : "") + (weight ? `font-weight: ${weight};` : "")
}
protected reset(): string {
return this.style("inherit", "normal")
}
public doWrite(level: Level, message: MessageFormat): void {
console.groupCollapsed(message.format, ...message.args)
message.fields.forEach((field) => {
this.push(field.identifier, "#3794ff", "bold")
if (
typeof field.value !== "undefined" &&
field.value.constructor &&
field.value.constructor.name
) {
this.push(` (${field.value.constructor.name})`)
}
this.push(": ")
this.push(field.value)
const m = this.flush()
doLog(level, m.format, ...m.args)
})
console.groupEnd()
}
}
/**
* Display logs on the command line using ANSI color codes. Fields are displayed
* in a single stringified object inline.
*/
export class ServerFormatter extends Formatter {
public constructor() {
super("%s", !!process.stdout.isTTY)
}
protected style(color?: string, weight?: string): string {
return (weight === "bold" ? "\u001B[1m" : "") + (color ? this.hex(color) : "")
}
protected reset(): string {
return "\u001B[0m"
}
private hex(hex: string): string {
const [r, g, b] = this.hexToRgb(hex)
return `\u001B[38;2;${r};${g};${b}m`
}
private hexToRgb(hex: string): [number, number, number] {
const integer = parseInt(hex.substr(1), 16)
return [(integer >> 16) & 0xff, (integer >> 8) & 0xff, integer & 0xff]
}
protected doWrite(level: Level, message: MessageFormat): void {
if (message.fields.length === 0) {
return doLog(level, "[%s] " + message.format, new Date().toISOString(), ...message.args)
}
const obj: { [key: string]: Argument } = {}
message.fields.forEach((field) => (obj[field.identifier] = field.value))
doLog(
level,
"[%s] " + message.format + " %s%s%s",
new Date().toISOString(),
...message.args,
this.style("#8c8c8c"),
JSON.stringify(obj),
this.reset(),
)
}
}
export class Logger {
public level = Level.Info
private readonly nameColor?: string
private muted = false
public constructor(
private _formatter: Formatter,
private readonly name?: string,
private readonly defaultFields?: FieldArray,
private readonly extenders: Extender[] = [],
) {
if (name) {
this.nameColor = this.hashStringToColor(name)
}
if (typeof process !== "undefined" && typeof process.env !== "undefined") {
switch (process.env.LOG_LEVEL) {
case "trace":
this.level = Level.Trace
break
case "debug":
this.level = Level.Debug
break
case "info":
this.level = Level.Info
break
case "warn":
this.level = Level.Warn
break
case "error":
this.level = Level.Error
break
}
}
}
public set formatter(formatter: Formatter) {
this._formatter = formatter
}
/**
* Supresses all output
*/
public mute(): void {
this.muted = true
}
public extend(extender: Extender): void {
this.extenders.push(extender)
}
public info(fn: LogCallback): void
public info(message: string, ...fields: FieldArray): void
public info(message: LogCallback | string, ...fields: FieldArray): void {
this.handle({
message,
fields,
tagColor: "#008FBF",
level: Level.Info,
})
}
public warn(fn: LogCallback): void
public warn(message: string, ...fields: FieldArray): void
public warn(message: LogCallback | string, ...fields: FieldArray): void {
this.handle({
message,
fields,
tagColor: "#FF9D00",
level: Level.Warn,
})
}
public trace(fn: LogCallback): void
public trace(message: string, ...fields: FieldArray): void
public trace(message: LogCallback | string, ...fields: FieldArray): void {
this.handle({
message,
fields,
tagColor: "#888888",
level: Level.Trace,
})
}
public debug(fn: LogCallback): void
public debug(message: string, ...fields: FieldArray): void
public debug(message: LogCallback | string, ...fields: FieldArray): void {
this.handle({
message,
fields,
tagColor: "#84009E",
level: Level.Debug,
})
}
public error(fn: LogCallback): void
public error(message: string, ...fields: FieldArray): void
public error(message: LogCallback | string, ...fields: FieldArray): void {
this.handle({
message,
fields,
tagColor: "#B00000",
level: Level.Error,
})
}
/**
* Returns a sub-logger with a name.
* Each name is deterministically generated a color.
*/
public named(name: string, ...fields: FieldArray): Logger {
const l = new Logger(this._formatter, name, fields, this.extenders)
if (this.muted) {
l.mute()
}
return l
}
private handle(message: Message): void {
if (this.level > message.level || this.muted) {
return
}
let passedFields = message.fields || []
if (typeof message.message === "function") {
const values = message.message()
message.message = values.shift() as string
passedFields = values as FieldArray
}
const fields = (
this.defaultFields
? passedFields.filter((f) => !!f).concat(this.defaultFields)
: passedFields.filter((f) => !!f)
) as Array<Field<Argument>>
const now = Date.now()
let times: Array<Field<Time>> = []
const hasFields = fields && fields.length > 0
if (hasFields) {
times = fields.filter((f) => f.value instanceof Time)
this._formatter.push(fields)
}
this._formatter.tag(Level[message.level].toLowerCase(), message.tagColor)
if (this.name && this.nameColor) {
this._formatter.tag(this.name, this.nameColor)
}
this._formatter.push(message.message)
if (times.length > 0) {
times.forEach((time) => {
const diff = now - time.value.ms
const expPer = diff / time.value.expected
const min = 125 * (1 - expPer)
const max = 125 + min
const green = expPer < 1 ? max : min
const red = expPer >= 1 ? max : min
this._formatter.push(` ${time.identifier}=`, "#3794ff")
this._formatter.push(
`${diff}ms`,
this.rgbToHex(red > 0 ? red : 0, green > 0 ? green : 0, 0),
)
})
}
this._formatter.write(message.level)
this.extenders.forEach((extender) => {
extender({
section: this.name,
...message,
})
})
}
/**
* Hashes a string.
*/
private djb2(str: string): number {
let hash = 5381
for (let i = 0; i < str.length; i++) {
hash = (hash << 5) + hash + str.charCodeAt(i) /* hash * 33 + c */
}
return hash
}
private rgbToHex(r: number, g: number, b: number): string {
const integer =
((Math.round(r) & 0xff) << 16) + ((Math.round(g) & 0xff) << 8) + (Math.round(b) & 0xff)
const str = integer.toString(16)
return "#" + "000000".substring(str.length) + str
}
/**
* Generates a deterministic color from a string using hashing.
*/
private hashStringToColor(str: string): string {
const hash = this.djb2(str)
return this.rgbToHex((hash & 0xff0000) >> 16, (hash & 0x00ff00) >> 8, hash & 0x0000ff)
}
}
export const logger = new Logger(
typeof process === "undefined" || typeof process.stdout === "undefined"
? new BrowserFormatter()
: new ServerFormatter(),
)