Skip to content

Commit

Permalink
logs
Browse files Browse the repository at this point in the history
  • Loading branch information
riccardobl committed Jan 7, 2025
1 parent 2bbf252 commit 19b7cf4
Show file tree
Hide file tree
Showing 3 changed files with 150 additions and 93 deletions.
14 changes: 14 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,20 @@ services:
labels:
CONNECT: "localhost:3000"
cpu_shares: "${CPU_SHARES_IMPORTANT}"
logpipe:
container_name: logpipe
image: ghcr.io/riccardobl/logpipe:0.0.5
restart: unless-stopped
healthcheck:
<<: *healthcheck
test: ["CMD", "curl", "-f", "http://localhost:7068/health"]
expose:
- "7068:7068"
ports:
- "7068"
tmpfs:
- /tmp
cpu_shares: "${CPU_SHARES_LOW}"
capture:
container_name: capture
build:
Expand Down
224 changes: 131 additions & 93 deletions lib/logger.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// ts-check
import { SSR } from '@/lib/constants'

const isBrowser = !SSR

export const LogLevel = {
TRACE: 600,
DEBUG: 500,
Expand All @@ -16,27 +18,48 @@ export const LogLevel = {
*/
export class LogAttachment {
/**
* Log something
* @param {Logger} logger - the logger that called this attachment
* @param {number} level - the log level
* @param {string[]} tags - the tags
* @param {...any} message - the message to log
* @abstract
* @protected
* @returns {Promise<void>}
*/
* Log something
* @param {Logger} logger - the logger that called this attachment
* @param {number} level - the log level
* @param {string[]} tags - the tags
* @param {...any} message - the message to log
* @abstract
* @protected
*/
log (logger, level, tags, ...message) {
throw new Error('not implemented')
throw new Error('Method not implemented.')
}
}

export class ConsoleLogAttachment extends LogAttachment {
level = undefined
/**
* @param {number} level
*/
constructor (level) {
super()
this.level = level
}

log (logger, level, tags, ...message) {
if (this.level && level > this.level) return

let head = ''
if (SSR) {
head += `[${new Date().toISOString()}] `
if (!isBrowser) {
const date = new Date()
const year = date.getFullYear()
const month = ('0' + (date.getMonth() + 1)).slice(-2)
const day = ('0' + date.getDate()).slice(-2)
const hour = ('0' + date.getHours()).slice(-2)
const minute = ('0' + date.getMinutes()).slice(-2)
const second = ('0' + date.getSeconds()).slice(-2)
head += `[${year}-${month}-${day} ${hour}:${minute}:${second}] `
}
head += `[${logger.name}] `
if (!isBrowser) {
head += `[${LogLevel[level]}] `
}
head += `[${logger.getName()}] `

const tail = tags.length ? ` ${tags.join(',')}` : ''
if (level <= LogLevel.ERROR) {
console.error(head, ...message, tail)
Expand All @@ -51,64 +74,59 @@ export class ConsoleLogAttachment extends LogAttachment {
}

export class JSONLogAttachment extends LogAttachment {
level = undefined
endpoint = null
util = undefined
constructor (endpoint, util) {

constructor (endpoint, level) {
super()
this.endpoint = endpoint
this.util = util
this.level = level
}

log (logger, level, tags, ...message) {
if (this.level && level > this.level) return

const serialize = (m) => {
if (typeof m === 'function') {
return m.toString() + '\n' + (new Error()).stack
} else if (typeof m === 'undefined') {
const type = typeof m
if (type === 'function') {
return m.toString() + '\n' + new Error().stack
} else if (type === 'undefined') {
return 'undefined'
} else if (m === null) {
return 'null'
} else if (typeof m === 'string') {
return m
} else if (typeof m === 'number' || typeof m === 'bigint') {
return m.toString()
} else if (type === 'string' || type === 'number' || type === 'bigint' || type === 'boolean') {
return String(m)
} else if (m instanceof Error) {
return m.message || m.toString()
} else if (m instanceof ArrayBuffer || m instanceof Uint8Array) {
return 'Buffer:' + Array.prototype.map.call(new Uint8Array(m), x => ('00' + x.toString(16)).slice(-2)).join('')
return 'Buffer:' + Array.prototype.map.call(new Uint8Array(m), (x) => ('00' + x.toString(16)).slice(-2)).join('')
} else if (type === 'object' && Array.isArray(m)) {
return JSON.stringify(m.map(serialize), null, 2)
} else {
try {
if (SSR && this.util) {
const inspected = this.util.inspect(m, { depth: 6 })
return inspected
}
const str = m.toString()
if (str !== '[object Object]') return str
} catch (e) {
console.error(e)
}
return JSON.stringify(m, null, 2)
}
}

const messageParts = message.map(m => {
return Promise.resolve(serialize(m))
})

const date = new Date().toISOString()
Promise.all(messageParts)
.then(parts => {
return fetch(this.endpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
logger: logger.getName(),
tags,
level: Object.entries(LogLevel).find(([k, v]) => v === level)[0],
message: parts.join(' '),
createdAt: date
})
})
}).catch(e => console.error('Error in JSONLogAttachment', e))
const logLevelStr = Object.entries(LogLevel).find(([k, v]) => v === level)[0]
fetch(this.endpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
logger: logger.name,
tags,
level: logLevelStr,
message: message.map((m) => serialize(m)).join(' '),
createdAt: new Date().toISOString()
})
}).catch((e) => console.error('Error in JSONLogAttachment', e))
}
}

Expand All @@ -120,67 +138,96 @@ export class JSONLogAttachment extends LogAttachment {
*/
export class Logger {
tags = []
globalTags = []
globalTags = {}
attachments = []
constructor (name, level, tags, globalTags) {
name = null
level = null
groupTags = []

/**
*
* @param {string} name
* @param {number} [level]
* @param {string[]} [tags]
* @param {{[key: string]: string}} [globalTags]
* @param {string[]} [groupTags]
*/
constructor (name, level, tags, globalTags, groupTags) {
this.name = name
this.tags.push(...tags)
this.globalTags = globalTags || {}
this.level = LogLevel[level.toUpperCase()] || LogLevel.INFO
}

getName () {
return this.name
this.level = level
if (groupTags) this.groupTags.push(...groupTags)
}

/**
* Add a log attachment
* @param {LogAttachment} attachment - the attachment to add
* @public
*/
* Add a log attachment
* @param {LogAttachment} attachment - the attachment to add
* @public
*/
addAttachment (attachment) {
this.attachments.push(attachment)
}

group (label) {
this.groupTags.push(label)
}

groupEnd () {
this.groupTags.pop()
}

fork (label) {
const logger = new Logger(this.name, this.level, [...this.tags], this.globalTags, [...this.groupTags, label])
for (const attachment of this.attachments) {
logger.addAttachment(attachment)
}
return logger
}

/**
* Log something
* @param {number} level - the log level
* @param {...any} message - the message to log
* @returns {Promise<any>}
* @public
*/
* Log something
* @param {number} level - the log level
* @param {...any} message - the message to log
* @public
*/
log (level, ...message) {
if (level > this.level) return
for (const attachment of this.attachments) {
try {
attachment.log(this, level, [...this.tags, ...Object.entries(this.globalTags).map(([k, v]) => `${k}:${v}`)], ...message)
attachment.log(this, level, [...this.tags, ...this.groupTags, ...Object.entries(this.globalTags).map(([k, v]) => `${k}:${v}`)], ...message)
} catch (e) {
console.error('Error in log attachment', e)
}
}
}

/**
* Log something lazily.
* @param {number} level - the log level
* @param {() => (string | string[] | Promise<string | string[]>)} func - The function to call (can be async, but better not)
* @returns {Promise<any>}
* @throws {Error} if func is not a function
* @public
*/
* Log something lazily.
* @param {number} level - the log level
* @param {() => (any|Promise<any>)} func - The function to call (can be async, but better not)
* @throws {Error} if func is not a function
* @public
*/
logLazy (level, func) {
if (typeof func !== 'function') {
throw new Error('lazy log needs a function to call')
}

if (level > this.level) return

try {
const res = func()

const _log = (message) => {
message = Array.isArray(message) ? message : [message]
this.log(level, ...message)
}

if (res instanceof Promise) {
res.then(_log).catch(e => this.error('Error in lazy log', e))
res.then(_log)
.catch((e) => this.error('Error in lazy log', e))
.catch((e) => console.error('Error in lazy log', e))
} else {
_log(res)
}
Expand Down Expand Up @@ -248,42 +295,33 @@ export function setGlobalLoggerTag (key, value) {
}
}

export function getLogger (name, tags, level) {
if (!name) {
throw new Error('name is required')
}

name = name || 'default'
tags = tags || []
if (!Array.isArray(tags)) {
tags = [tags]
}
export function getLogger (name = 'default', tags = [], level) {
if (!Array.isArray(tags)) tags = [tags]

let httpEndpoint = SSR ? 'http://logpipe:7068/write' : 'http://localhost:7068/write'
let httpEndpoint = !isBrowser ? 'http://logpipe:7068/write' : 'http://localhost:7068/write'
let env = 'production'

if (typeof process !== 'undefined') {
env = process.env.NODE_ENV || env
httpEndpoint = process.env.SN_LOG_HTTP_ENDPOINT || httpEndpoint
level = level ?? process.env.SN_LOG_LEVEL
}
level = level ?? env === 'development' ? 'TRACE' : 'INFO'
level = level ?? (env === 'development' ? 'TRACE' : 'INFO')

// test
httpEndpoint = 'https://logpipe.frk.wf/write'

if (SSR) {
if (!isBrowser) {
tags.push('backend')
} else {
tags.push('frontend')
}

const logger = new Logger(name, level, tags, globalLoggerTags)
logger.addAttachment(new ConsoleLogAttachment())
const logger = new Logger(name, LogLevel[level], tags, globalLoggerTags)

if (env === 'development') {
logger.addAttachment(new ConsoleLogAttachment())
logger.addAttachment(new JSONLogAttachment(httpEndpoint))
} else {
logger.addAttachment(new ConsoleLogAttachment())
}

return logger
}
5 changes: 5 additions & 0 deletions sndev
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,10 @@ sndev__logs() {
docker__compose logs "$@"
}

sndev__logview() {
docker__compose exec -it logpipe bash /app/scripts/stream.sh
}

sndev__help_logs() {
help="
get logs from sndev env
Expand Down Expand Up @@ -593,6 +597,7 @@ COMMANDS
restart restart env
status status of env
logs logs from env
logview stream logs from the app
delete delete env
sn:
Expand Down

0 comments on commit 19b7cf4

Please sign in to comment.