chore: import upstream snapshot with attribution
Publish CLI Package / publish-npm (push) Waiting to run
Publish Python SDK / publish-pypi (push) Waiting to run
Publish TypeScript SDK / publish-npm (push) Waiting to run
CI / Migrate Dev DB (push) Has been skipped
CI / Detect Version (push) Has been cancelled
CI / Migrate DB (push) Has been cancelled
CI / Build Dev ECR (./docker/app.Dockerfile, ECR_APP) (push) Has been cancelled
CI / Build Dev ECR (./docker/db.Dockerfile, ECR_MIGRATIONS) (push) Has been cancelled
CI / Build Dev ECR (./docker/pii.Dockerfile, ECR_PII) (push) Has been cancelled
CI / Build Dev ECR (./docker/realtime.Dockerfile, ECR_REALTIME) (push) Has been cancelled
CI / Deploy Trigger.dev (Dev) (push) Has been cancelled
CI / Build AMD64 (./docker/app.Dockerfile, ECR_APP, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build AMD64 (./docker/db.Dockerfile, ECR_MIGRATIONS, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build AMD64 (./docker/pii.Dockerfile, ECR_PII, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build AMD64 (./docker/realtime.Dockerfile, ECR_REALTIME, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/app.Dockerfile, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/db.Dockerfile, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/pii.Dockerfile, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/realtime.Dockerfile, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Check Docs Changes (push) Has been cancelled
CI / Process Docs (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
CI / Test and Build (push) Has been cancelled

This commit is contained in:
wehub-resource-sync
2026-07-13 13:20:55 +08:00
commit d25d482dc2
13754 changed files with 4996608 additions and 0 deletions
+37
View File
@@ -0,0 +1,37 @@
{
"name": "@sim/logger",
"version": "0.1.0",
"private": true,
"sideEffects": false,
"type": "module",
"license": "Apache-2.0",
"engines": {
"bun": ">=1.2.13",
"node": ">=20.0.0"
},
"exports": {
".": {
"types": "./src/index.ts",
"default": "./src/index.ts"
}
},
"scripts": {
"type-check": "tsc --noEmit",
"lint": "biome check --write --unsafe .",
"lint:check": "biome check .",
"format": "biome format --write .",
"format:check": "biome format .",
"test": "vitest run",
"test:watch": "vitest"
},
"dependencies": {
"@sim/utils": "workspace:*",
"chalk": "5.6.2"
},
"devDependencies": {
"@sim/tsconfig": "workspace:*",
"@types/node": "24.2.1",
"typescript": "^7.0.2",
"vitest": "^4.1.0"
}
}
+219
View File
@@ -0,0 +1,219 @@
import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest'
import { createLogger, Logger, LogLevel } from './index'
/**
* Tests for the console logger module.
* Tests the Logger class and createLogger factory function.
*/
describe('Logger', () => {
let consoleLogSpy: ReturnType<typeof vi.spyOn>
let consoleErrorSpy: ReturnType<typeof vi.spyOn>
beforeEach(() => {
consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => {})
consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {})
})
afterEach(() => {
consoleLogSpy.mockRestore()
consoleErrorSpy.mockRestore()
vi.clearAllMocks()
})
describe('class instantiation', () => {
test('should create logger instance with module name', () => {
const logger = new Logger('TestModule')
expect(logger).toBeDefined()
expect(logger).toBeInstanceOf(Logger)
})
})
describe('createLogger factory', () => {
test('should create logger instance with expected methods', () => {
const logger = createLogger('MyComponent')
expect(logger).toBeDefined()
expect(typeof logger.debug).toBe('function')
expect(typeof logger.info).toBe('function')
expect(typeof logger.warn).toBe('function')
expect(typeof logger.error).toBe('function')
})
test('should create multiple independent loggers', () => {
const logger1 = createLogger('Component1')
const logger2 = createLogger('Component2')
expect(logger1).not.toBe(logger2)
})
})
describe('LogLevel enum', () => {
test('should have correct log levels', () => {
expect(LogLevel.DEBUG).toBe('DEBUG')
expect(LogLevel.INFO).toBe('INFO')
expect(LogLevel.WARN).toBe('WARN')
expect(LogLevel.ERROR).toBe('ERROR')
})
})
describe('logging methods', () => {
test('should have debug method', () => {
const logger = createLogger('TestModule')
expect(typeof logger.debug).toBe('function')
})
test('should have info method', () => {
const logger = createLogger('TestModule')
expect(typeof logger.info).toBe('function')
})
test('should have warn method', () => {
const logger = createLogger('TestModule')
expect(typeof logger.warn).toBe('function')
})
test('should have error method', () => {
const logger = createLogger('TestModule')
expect(typeof logger.error).toBe('function')
})
})
describe('logging behavior', () => {
test('should not throw when calling debug', () => {
const logger = createLogger('TestModule')
expect(() => logger.debug('Test debug message')).not.toThrow()
})
test('should not throw when calling info', () => {
const logger = createLogger('TestModule')
expect(() => logger.info('Test info message')).not.toThrow()
})
test('should not throw when calling warn', () => {
const logger = createLogger('TestModule')
expect(() => logger.warn('Test warn message')).not.toThrow()
})
test('should not throw when calling error', () => {
const logger = createLogger('TestModule')
expect(() => logger.error('Test error message')).not.toThrow()
})
})
describe('object formatting', () => {
test('should handle null and undefined arguments', () => {
const logger = createLogger('TestModule')
expect(() => {
logger.info('Message with null:', null)
logger.info('Message with undefined:', undefined)
}).not.toThrow()
})
test('should handle object arguments', () => {
const logger = createLogger('TestModule')
const testObj = { key: 'value', nested: { data: 123 } }
expect(() => {
logger.info('Message with object:', testObj)
}).not.toThrow()
})
test('should handle Error objects', () => {
const logger = createLogger('TestModule')
const testError = new Error('Test error message')
expect(() => {
logger.error('An error occurred:', testError)
}).not.toThrow()
})
test('should handle circular references gracefully', () => {
const logger = createLogger('TestModule')
const circularObj: Record<string, unknown> = { name: 'test' }
circularObj.self = circularObj
expect(() => {
logger.info('Circular object:', circularObj)
}).not.toThrow()
})
test('should handle arrays', () => {
const logger = createLogger('TestModule')
const testArray = [1, 2, 3, { nested: true }]
expect(() => {
logger.info('Array data:', testArray)
}).not.toThrow()
})
test('should handle multiple arguments', () => {
const logger = createLogger('TestModule')
expect(() => {
logger.debug('Multiple args:', 'string', 123, { obj: true }, ['array'])
}).not.toThrow()
})
})
describe('withMetadata', () => {
const createEnabledLogger = () =>
new Logger('Test', { enabled: true, colorize: false, logLevel: LogLevel.DEBUG })
test('should return a new Logger instance', () => {
const logger = createEnabledLogger()
const child = logger.withMetadata({ workflowId: 'wf_1' })
expect(child).toBeInstanceOf(Logger)
expect(child).not.toBe(logger)
})
test('should include metadata in log output', () => {
const child = createEnabledLogger().withMetadata({ workflowId: 'wf_1' })
child.info('hello')
expect(consoleLogSpy).toHaveBeenCalledTimes(1)
const parsed = JSON.parse(consoleLogSpy.mock.calls[0][0] as string)
expect(parsed.workflowId).toBe('wf_1')
expect(parsed.message).toBe('hello')
})
test('should not affect original logger output', () => {
const logger = createEnabledLogger()
logger.withMetadata({ workflowId: 'wf_1' })
logger.info('hello')
const parsed = JSON.parse(consoleLogSpy.mock.calls[0][0] as string)
expect(parsed.workflowId).toBeUndefined()
})
test('should merge metadata across chained calls', () => {
const child = createEnabledLogger().withMetadata({ a: '1' }).withMetadata({ b: '2' })
child.info('hello')
const parsed = JSON.parse(consoleLogSpy.mock.calls[0][0] as string)
expect(parsed.a).toBe('1')
expect(parsed.b).toBe('2')
})
test('should override parent metadata for same key', () => {
const child = createEnabledLogger().withMetadata({ a: '1' }).withMetadata({ a: '2' })
child.info('hello')
const parsed = JSON.parse(consoleLogSpy.mock.calls[0][0] as string)
expect(parsed.a).toBe('2')
})
test('should exclude undefined values from output', () => {
const child = createEnabledLogger().withMetadata({ a: '1', b: undefined })
child.info('hello')
const parsed = JSON.parse(consoleLogSpy.mock.calls[0][0] as string)
expect(parsed.a).toBe('1')
expect(parsed.b).toBeUndefined()
})
test('should produce no metadata segment when metadata is empty', () => {
const child = createEnabledLogger().withMetadata({})
child.info('hello')
const parsed = JSON.parse(consoleLogSpy.mock.calls[0][0] as string)
expect(parsed.message).toBe('hello')
expect(Object.keys(parsed)).toEqual(
expect.arrayContaining(['timestamp', 'level', 'module', 'message'])
)
})
})
})
+380
View File
@@ -0,0 +1,380 @@
/**
* @sim/logger
*
* Framework-agnostic logging utilities for the Sim platform.
* Provides standardized console logging with environment-aware configuration.
*/
import { filterUndefined } from '@sim/utils/object'
import chalk from 'chalk'
import { getRequestContext } from './request-context'
/**
* LogLevel enum defines the severity levels for logging
*
* DEBUG: Detailed information, typically useful only for diagnosing problems
* INFO: Confirmation that things are working as expected
* WARN: Indication that something unexpected happened
* ERROR: Error events that might still allow the application to continue running
*/
export enum LogLevel {
DEBUG = 'DEBUG',
INFO = 'INFO',
WARN = 'WARN',
ERROR = 'ERROR',
}
/**
* Logger configuration options
*/
export interface LoggerConfig {
/** Minimum log level to display */
logLevel?: LogLevel | string
/** Whether to colorize output */
colorize?: boolean
/** Whether logging is enabled */
enabled?: boolean
}
/**
* Metadata key-value pairs attached to a logger instance.
* Included automatically in every log line produced by that logger.
*/
export type LoggerMetadata = Record<string, string | number | boolean | undefined>
const getNodeEnv = (): string => {
if (typeof process !== 'undefined' && process.env) {
return process.env.NODE_ENV || 'development'
}
return 'development'
}
const getLogLevel = (): string | undefined => {
if (typeof process !== 'undefined' && process.env) {
return process.env.LOG_LEVEL
}
return undefined
}
/**
* Get the minimum log level from environment variable or use defaults
* - Development: DEBUG (show all logs)
* - Production: ERROR (only show errors, but can be overridden by LOG_LEVEL env var)
* - Test: ERROR (only show errors in tests)
*/
const getMinLogLevel = (): LogLevel => {
const logLevelEnv = getLogLevel()
if (logLevelEnv && Object.values(LogLevel).includes(logLevelEnv as LogLevel)) {
return logLevelEnv as LogLevel
}
const nodeEnv = getNodeEnv()
switch (nodeEnv) {
case 'development':
return LogLevel.DEBUG
case 'production':
return LogLevel.ERROR
case 'test':
return LogLevel.ERROR
default:
return LogLevel.DEBUG
}
}
/**
* Configuration for different environments
*/
const getLogConfig = () => {
const nodeEnv = getNodeEnv()
const minLevel = getMinLogLevel()
switch (nodeEnv) {
case 'development':
return {
enabled: true,
minLevel,
colorize: true,
}
case 'production':
return {
enabled: true,
minLevel,
colorize: false,
}
case 'test':
return {
enabled: false,
minLevel,
colorize: false,
}
default:
return {
enabled: true,
minLevel,
colorize: true,
}
}
}
/**
* Format objects for logging
*/
const formatObject = (obj: unknown, isDev: boolean): string => {
try {
if (obj instanceof Error) {
const errorObj: Record<string, unknown> = {
message: obj.message,
stack: isDev ? obj.stack : undefined,
name: obj.name,
}
for (const key of Object.keys(obj)) {
if (!(key in errorObj)) {
errorObj[key] = (obj as unknown as Record<string, unknown>)[key]
}
}
return JSON.stringify(errorObj, null, isDev ? 2 : 0)
}
return JSON.stringify(obj, null, isDev ? 2 : 0)
} catch {
return '[Circular or Non-Serializable Object]'
}
}
/**
* Logger class for standardized console logging
*
* Provides methods for logging at different severity levels
* and handles formatting, colorization, and environment-specific behavior.
*/
export class Logger {
private module: string
private config: ReturnType<typeof getLogConfig>
private isDev: boolean
private metadata: LoggerMetadata = {}
/**
* Create a new logger for a specific module
* @param module The name of the module (e.g., 'OpenAIProvider', 'AgentBlockHandler')
* @param overrideConfig Optional configuration overrides
*/
constructor(module: string, overrideConfig?: LoggerConfig) {
this.module = module
this.config = getLogConfig()
this.isDev = getNodeEnv() === 'development'
// Apply overrides if provided
if (overrideConfig) {
if (overrideConfig.logLevel !== undefined) {
const level =
typeof overrideConfig.logLevel === 'string'
? (overrideConfig.logLevel as LogLevel)
: overrideConfig.logLevel
if (Object.values(LogLevel).includes(level)) {
this.config.minLevel = level
}
}
if (overrideConfig.colorize !== undefined) {
this.config.colorize = overrideConfig.colorize
}
if (overrideConfig.enabled !== undefined) {
this.config.enabled = overrideConfig.enabled
}
}
}
/**
* Creates a child logger with additional metadata merged in.
* The child inherits this logger's module name, config, and existing metadata.
* New metadata keys override existing ones with the same name.
*/
withMetadata(metadata: LoggerMetadata): Logger {
const child = Object.create(Logger.prototype) as Logger
child.module = this.module
child.config = this.config
child.isDev = this.isDev
child.metadata = { ...this.metadata, ...metadata }
return child
}
/**
* Determines if a log at the given level should be displayed
*/
private shouldLog(level: LogLevel): boolean {
if (!this.config.enabled) return false
if (
getNodeEnv() === 'production' &&
typeof (globalThis as { window?: unknown }).window !== 'undefined'
) {
return false
}
const levels = [LogLevel.DEBUG, LogLevel.INFO, LogLevel.WARN, LogLevel.ERROR]
const minLevelIndex = levels.indexOf(this.config.minLevel)
const currentLevelIndex = levels.indexOf(level)
return currentLevelIndex >= minLevelIndex
}
/**
* Format arguments for logging, converting objects to JSON strings
*/
private formatArgs(args: unknown[]): unknown[] {
return args.map((arg) => {
if (arg === null || arg === undefined) return arg
if (typeof arg === 'object') return formatObject(arg, this.isDev)
return arg
})
}
/**
* Internal method to log a message with the specified level
*/
private log(level: LogLevel, message: string, ...args: unknown[]) {
if (!this.shouldLog(level)) return
const timestamp = new Date().toISOString()
const formattedArgs = this.formatArgs(args)
const reqCtx = getRequestContext()
const effectiveMetadata = reqCtx
? {
requestId: reqCtx.requestId,
method: reqCtx.method,
path: reqCtx.path,
...this.metadata,
}
: this.metadata
const metadataEntries = Object.entries(filterUndefined(effectiveMetadata))
const metadataStr =
metadataEntries.length > 0
? ` {${metadataEntries.map(([k, v]) => `${k}=${v}`).join(' ')}}`
: ''
if (this.config.colorize) {
let levelColor: (text: string) => string
const moduleColor = chalk.cyan
const timestampColor = chalk.gray
switch (level) {
case LogLevel.DEBUG:
levelColor = chalk.blue
break
case LogLevel.INFO:
levelColor = chalk.green
break
case LogLevel.WARN:
levelColor = chalk.yellow
break
case LogLevel.ERROR:
levelColor = chalk.red
break
}
const coloredMeta = metadataStr ? ` ${chalk.magenta(metadataStr.trim())}` : ''
const coloredPrefix = `${timestampColor(`[${timestamp}]`)} ${levelColor(`[${level}]`)} ${moduleColor(`[${this.module}]`)}${coloredMeta}`
if (level === LogLevel.ERROR) {
console.error(coloredPrefix, message, ...formattedArgs)
} else {
console.log(coloredPrefix, message, ...formattedArgs)
}
} else {
// Structured JSON for production — CloudWatch Log Insights auto-parses JSON lines
const entry: Record<string, unknown> = {
timestamp,
level,
module: this.module,
message,
}
for (const [k, v] of metadataEntries) {
entry[k] = v
}
// Merge extra args into the entry
for (const arg of args) {
if (
arg !== null &&
arg !== undefined &&
typeof arg === 'object' &&
!(arg instanceof Error)
) {
Object.assign(entry, arg)
} else if (arg instanceof Error) {
entry.error = arg.message
entry.stack = arg.stack
} else if (arg !== null && arg !== undefined) {
entry.extra = arg
}
}
const line = JSON.stringify(entry)
if (level === LogLevel.ERROR) {
console.error(line)
} else {
console.log(line)
}
}
}
/**
* Log a debug message
*
* Use for detailed information useful during development and debugging.
* These logs are only shown in development environment by default.
*/
debug(message: string, ...args: unknown[]) {
this.log(LogLevel.DEBUG, message, ...args)
}
/**
* Log an info message
*
* Use for general information about application operation.
*/
info(message: string, ...args: unknown[]) {
this.log(LogLevel.INFO, message, ...args)
}
/**
* Log a warning message
*
* Use for potentially problematic situations that don't cause operation failure.
*/
warn(message: string, ...args: unknown[]) {
this.log(LogLevel.WARN, message, ...args)
}
/**
* Log an error message
*
* Use for error events that might still allow the application to continue.
*/
error(message: string, ...args: unknown[]) {
this.log(LogLevel.ERROR, message, ...args)
}
}
/**
* Create a logger for a specific module
*
* @example
* ```typescript
* import { createLogger } from '@sim/logger'
*
* const logger = createLogger('MyComponent')
*
* logger.debug('Initializing component', { props })
* logger.info('Component mounted')
* logger.warn('Deprecated prop used', { propName })
* logger.error('Failed to fetch data', error)
* ```
*
* @param module The name of the module
* @param config Optional configuration overrides
* @returns A Logger instance
*/
export function createLogger(module: string, config?: LoggerConfig): Logger {
return new Logger(module, config)
}
export type { RequestContext } from './request-context'
export { getRequestContext, runWithRequestContext } from './request-context'
+46
View File
@@ -0,0 +1,46 @@
export interface RequestContext {
requestId: string
method?: string
path?: string
}
/**
* AsyncLocalStorage is only available in Node.js. In Edge/browser contexts
* we fall back to a no-op implementation so the logger import doesn't break.
*/
interface Storage<T> {
getStore(): T | undefined
run<R>(store: T, fn: () => R): R
}
let storage: Storage<RequestContext>
if (typeof globalThis.process !== 'undefined' && globalThis.process.versions?.node) {
// Node.js — use real AsyncLocalStorage
// eslint-disable-next-line @typescript-eslint/no-require-imports
const { AsyncLocalStorage } = require('node:async_hooks') as typeof import('node:async_hooks')
storage = new AsyncLocalStorage<RequestContext>()
} else {
// Edge / browser — no-op
storage = {
getStore: () => undefined,
run: <R>(_store: RequestContext, fn: () => R) => fn(),
}
}
/**
* Runs a callback within a request context. All loggers called inside
* the callback (and any async functions it awaits) will automatically
* include the request context metadata in their output.
*/
export function runWithRequestContext<T>(context: RequestContext, fn: () => T): T {
return storage.run(context, fn)
}
/**
* Returns the current request context, or undefined if called outside
* of a `runWithRequestContext` scope.
*/
export function getRequestContext(): RequestContext | undefined {
return storage.getStore()
}
+5
View File
@@ -0,0 +1,5 @@
{
"extends": "@sim/tsconfig/library.json",
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}
+9
View File
@@ -0,0 +1,9 @@
import { defineConfig } from 'vitest/config'
export default defineConfig({
test: {
globals: false,
environment: 'node',
include: ['src/**/*.test.ts'],
},
})