tslog
Logging library designed specifically for TypeScript (183,000 weekly downloads, 1,410 GitHub stars). Fully integrated with TypeScript type system, provides beautiful console output and browser support. Design emphasizing developer experience.
Logging Library
tslog
Overview
tslog is a universal logging library for TypeScript and JavaScript. It works in both browser and Node.js environments and features beautiful output formats, high performance, and customizable transports. From development-friendly pretty output to production-ready JSON format, output formats can be switched according to usage, supporting a wide range of output destinations including rotating file streams, database connections, and external API integration.
Details
tslog was designed to solve challenges in modern TypeScript/JavaScript development. As a replacement for traditional console.log, it provides type safety, structured logging, and flexible output control. Through integration with AsyncLocalStorage, automatic request ID tracking is possible, improving traceability in microservices and distributed systems.
Technical Features
- Universal: Supports Node.js, browser, Deno, React Native
- Full TypeScript Support: Complete type safety and type inference
- Customizable Output: Three basic formats: pretty, json, hidden
- Transport System: Flexible connection to custom output destinations
- AsyncLocalStorage Integration: Automatic context tracking
- Performance Optimization: Minimal overhead and memory usage
- Source Map Support: Accurate location display in stack traces
Output Formats
- pretty: Visually appealing colored output for development
- json: Easily parseable JSON format for production environments
- hidden: Disables log output (for testing environments)
Main Components
- Logger: Main logging class
- ILogObj: Log object interface
- ITransportProvider: Custom transport abstraction
- ILogObjMeta: Log metadata interface
Pros and Cons
Pros
- Full TypeScript Support: Type safety and excellent development experience
- Universal: Unified usage across multiple runtime environments
- High Performance: Minimal overhead and efficient processing
- Flexible Output: Ability to choose appropriate output formats from development to production
- Transport: Rich output destination options and customizability
- AsyncLocalStorage: Automatic request tracking
- JSON Output: Excellent searchability and parseability through structured logging
- Lightweight: Easy adoption with few additional dependencies
Cons
- Learning Curve: Time required to master advanced features
- Configuration Complexity: Can become complex with large-scale configurations
- Ecosystem: Fewer surrounding tools compared to Winston, etc.
- Documentation: Insufficient explanation for some advanced features
- Compatibility: Configuration adjustments required when migrating from other libraries
- Debugging: Difficult to identify issues with transport functionality in some cases
Reference Links
- Official Documentation: https://tslog.js.org/
- GitHub Repository: https://github.com/fullstack-build/tslog
- npm: https://www.npmjs.com/package/tslog
- TypeScript Official: TypeScript logging best practices
- Sample Code: Examples in the official repository
Usage Examples
Basic Usage
import { Logger } from "tslog";
// Create basic logger
const logger = new Logger();
// Basic log output
logger.info("Hello World!");
logger.warn("Warning message");
logger.error("An error occurred");
// Structured log data
logger.info("User login", {
userId: 12345,
username: "john_doe",
timestamp: new Date(),
sessionId: "abc123"
});
// Usage examples for each log level
logger.silly("Detailed debug information");
logger.trace("Tracing information");
logger.debug("Debug information");
logger.info("General information");
logger.warn("Warning message");
logger.error("Error information");
logger.fatal("Fatal error");
Configuration and Customization
import { Logger, ILogObj } from "tslog";
// Development environment configuration
const devLogger = new Logger({
name: "MyApp",
minLevel: "debug",
type: "pretty",
prettyLogTemplate: "{{yyyy}}.{{mm}}.{{dd}} {{hh}}:{{MM}}:{{ss}}:{{ms}}\t{{logLevelName}}\t[{{name}}]\t",
prettyErrorTemplate: "\n{{errorName}} {{errorMessage}}\nerror stack:\n{{errorStack}}",
prettyErrorStackTemplate: " • {{fileName}}:{{lineNumber}}:{{columnNumber}}\t{{functionName}}()",
prettyErrorParentNamesSeparator: ":",
prettyErrorLoggerNameDelimiter: "\t",
stylePrettyLogs: true,
prettyLogStyles: {
logLevelName: {
"*": ["bold", "black", "bgWhiteBright", "dim"],
FATAL: ["bold", "red"],
ERROR: ["bold", "red"],
WARN: ["bold", "yellow"],
INFO: ["bold", "white"],
DEBUG: ["bold", "green"],
TRACE: ["bold", "whiteBright"],
SILLY: ["bold", "white"]
},
dateIsoStr: "white",
filePathWithLine: "white",
name: ["white", "bold"],
nameWithDelimiterPrefix: ["white", "bold"],
nameWithDelimiterSuffix: ["white", "bold"],
errorName: ["bold", "bgRedBright", "whiteBright"],
fileName: ["yellow"]
}
});
// Production environment configuration
const prodLogger = new Logger({
name: "MyApp-Production",
minLevel: "info",
type: "json",
maskValuesOfKeys: ["password", "authorization", "cookie"],
maskPlaceholder: "[HIDDEN]",
exposeErrorCodeFrame: false
});
// Conditional configuration
const logger = process.env.NODE_ENV === "production" ? prodLogger : devLogger;
// Configuration changes
logger.settings.minLevel = "warn";
logger.settings.type = "json";
Transport Functionality
import { Logger, ILogObj } from "tslog";
import fs from "fs";
import path from "path";
// File output transport
function logToFile(logObj: ILogObj) {
const logFilePath = path.join(__dirname, 'logs', 'app.log');
const logMessage = JSON.stringify(logObj) + '\n';
fs.appendFileSync(logFilePath, logMessage);
}
// Database output transport
function logToDatabase(logObj: ILogObj) {
// Database connection and log storage
const logEntry = {
timestamp: logObj._meta.date,
level: logObj._meta.logLevelName,
message: logObj[0],
data: logObj[1] || {},
source: logObj._meta.path?.fileNameWithLine
};
// Database storage processing
// database.logs.insert(logEntry);
}
// HTTP API transport
function logToAPI(logObj: ILogObj) {
const payload = {
level: logObj._meta.logLevelName,
timestamp: logObj._meta.date.toISOString(),
message: logObj[0],
metadata: logObj[1] || {},
service: "my-app",
environment: process.env.NODE_ENV
};
// Send to API asynchronously
fetch('https://api.example.com/logs', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload)
}).catch(console.error);
}
// Create logger with transports
const logger = new Logger({
name: "TransportLogger",
type: "json"
});
// Register multiple transports
logger.attachTransport([
{
silly: logToFile,
debug: logToFile,
trace: logToFile,
info: logToFile,
warn: logToFile,
error: logToFile,
fatal: logToFile,
},
{
error: logToDatabase,
fatal: logToDatabase,
},
{
warn: logToAPI,
error: logToAPI,
fatal: logToAPI,
}
]);
// Log output (automatically sent to all transports)
logger.info("User registration", { userId: 123, email: "[email protected]" });
logger.error("Database connection error", { error: "Connection timeout" });
AsyncLocalStorage Integration
import { Logger, ILogObj } from "tslog";
import { AsyncLocalStorage } from "async_hooks";
import express from "express";
// Request context type definition
interface RequestContext {
requestId: string;
userId?: string;
traceId: string;
}
// AsyncLocalStorage setup
const requestContext = new AsyncLocalStorage<RequestContext>();
// Transport that automatically adds context information
function contextEnhancedTransport(logObj: ILogObj) {
const context = requestContext.getStore();
if (context) {
// Add context information to log object
const enhancedLogObj = {
...logObj,
requestId: context.requestId,
userId: context.userId,
traceId: context.traceId
};
console.log(JSON.stringify(enhancedLogObj));
} else {
console.log(JSON.stringify(logObj));
}
}
// Logger configuration
const logger = new Logger({
name: "ContextLogger",
type: "json"
});
logger.attachTransport({
silly: contextEnhancedTransport,
debug: contextEnhancedTransport,
trace: contextEnhancedTransport,
info: contextEnhancedTransport,
warn: contextEnhancedTransport,
error: contextEnhancedTransport,
fatal: contextEnhancedTransport
});
// Express middleware
const app = express();
app.use((req, res, next) => {
const context: RequestContext = {
requestId: `req_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
traceId: req.headers['x-trace-id'] as string || `trace_${Date.now()}`,
userId: req.headers['x-user-id'] as string
};
requestContext.run(context, () => {
logger.info("Request started", {
method: req.method,
url: req.url,
userAgent: req.headers['user-agent']
});
next();
});
});
// Route handler
app.get('/users/:id', (req, res) => {
const userId = req.params.id;
// This log automatically includes requestId, traceId, userId
logger.info("User data retrieval", { targetUserId: userId });
try {
// Business logic
const user = getUserById(userId);
logger.info("User data retrieval successful", { user: user.id });
res.json(user);
} catch (error) {
logger.error("User data retrieval error", { error: error.message });
res.status(500).json({ error: "Internal Server Error" });
}
});
Log Filtering and Formatting
import { Logger, ILogObj } from "tslog";
// Custom log object type definition
interface CustomLogObj extends ILogObj {
service?: string;
version?: string;
environment?: string;
}
// Filtering function
function sensitiveDataFilter(logObj: ILogObj): ILogObj {
const filtered = { ...logObj };
// Masking sensitive data
if (filtered[1] && typeof filtered[1] === 'object') {
const data = { ...filtered[1] };
['password', 'token', 'secret', 'key', 'authorization'].forEach(field => {
if (data[field]) {
data[field] = '[REDACTED]';
}
});
// Deep nested masking
Object.keys(data).forEach(key => {
if (typeof data[key] === 'object' && data[key] !== null) {
data[key] = maskSensitiveFields(data[key]);
}
});
filtered[1] = data;
}
return filtered;
}
function maskSensitiveFields(obj: any): any {
if (typeof obj !== 'object' || obj === null) return obj;
const masked = { ...obj };
['password', 'token', 'secret', 'key', 'authorization'].forEach(field => {
if (masked[field]) {
masked[field] = '[REDACTED]';
}
});
return masked;
}
// Custom formatter
function structuredLogFormatter(logObj: ILogObj) {
const structured = {
'@timestamp': logObj._meta.date.toISOString(),
'@version': '1',
level: logObj._meta.logLevelName,
logger: logObj._meta.name,
thread: `main`,
message: logObj[0],
fields: logObj[1] || {},
location: {
file: logObj._meta.path?.fileName,
line: logObj._meta.path?.lineNumber,
method: logObj._meta.path?.functionName
},
service: {
name: process.env.SERVICE_NAME || 'unknown',
version: process.env.SERVICE_VERSION || '1.0.0',
environment: process.env.NODE_ENV || 'development'
}
};
return JSON.stringify(structured);
}
// Logger configuration
const logger = new Logger({
name: "StructuredLogger",
type: "json",
minLevel: "info"
});
// Transport with filtering and formatting applied
logger.attachTransport({
silly: (logObj) => {
const filtered = sensitiveDataFilter(logObj);
const formatted = structuredLogFormatter(filtered);
console.log(formatted);
},
debug: (logObj) => {
const filtered = sensitiveDataFilter(logObj);
const formatted = structuredLogFormatter(filtered);
console.log(formatted);
},
trace: (logObj) => {
const filtered = sensitiveDataFilter(logObj);
const formatted = structuredLogFormatter(filtered);
console.log(formatted);
},
info: (logObj) => {
const filtered = sensitiveDataFilter(logObj);
const formatted = structuredLogFormatter(filtered);
console.log(formatted);
},
warn: (logObj) => {
const filtered = sensitiveDataFilter(logObj);
const formatted = structuredLogFormatter(filtered);
console.warn(formatted);
},
error: (logObj) => {
const filtered = sensitiveDataFilter(logObj);
const formatted = structuredLogFormatter(filtered);
console.error(formatted);
},
fatal: (logObj) => {
const filtered = sensitiveDataFilter(logObj);
const formatted = structuredLogFormatter(filtered);
console.error(formatted);
}
});
// Usage example
logger.info("User authentication successful", {
userId: 12345,
username: "john_doe",
password: "secret123", // Automatically masked
authToken: "abc123", // Automatically masked
loginTime: new Date()
});
Testing and Mocking
import { Logger, ILogObj } from "tslog";
import { jest } from '@jest/globals';
// Mock logger for testing
class MockLogger extends Logger {
public logs: ILogObj[] = [];
constructor() {
super({
name: "MockLogger",
type: "hidden" // Disable log output
});
// Transport that records all logs
this.attachTransport({
silly: (logObj) => this.logs.push({ ...logObj, level: 'silly' }),
debug: (logObj) => this.logs.push({ ...logObj, level: 'debug' }),
trace: (logObj) => this.logs.push({ ...logObj, level: 'trace' }),
info: (logObj) => this.logs.push({ ...logObj, level: 'info' }),
warn: (logObj) => this.logs.push({ ...logObj, level: 'warn' }),
error: (logObj) => this.logs.push({ ...logObj, level: 'error' }),
fatal: (logObj) => this.logs.push({ ...logObj, level: 'fatal' })
});
}
// Helper methods for testing
getLogsByLevel(level: string): ILogObj[] {
return this.logs.filter(log => log.level === level);
}
getLogsByMessage(message: string): ILogObj[] {
return this.logs.filter(log => log[0]?.includes(message));
}
clearLogs(): void {
this.logs = [];
}
getLastLog(): ILogObj | undefined {
return this.logs[this.logs.length - 1];
}
}
// Service under test
class UserService {
constructor(private logger: Logger) {}
async createUser(userData: { name: string; email: string }) {
this.logger.info("User creation started", { userData });
try {
// User creation logic
const user = { id: 123, ...userData };
this.logger.info("User creation successful", { userId: user.id });
return user;
} catch (error) {
this.logger.error("User creation error", { error: error.message });
throw error;
}
}
}
// Jest tests
describe('UserService', () => {
let mockLogger: MockLogger;
let userService: UserService;
beforeEach(() => {
mockLogger = new MockLogger();
userService = new UserService(mockLogger);
});
afterEach(() => {
mockLogger.clearLogs();
});
test('should output appropriate logs when creating user', async () => {
const userData = { name: 'John Doe', email: '[email protected]' };
await userService.createUser(userData);
// Verify logs
const infoLogs = mockLogger.getLogsByLevel('info');
expect(infoLogs).toHaveLength(2);
// Verify start log
expect(infoLogs[0][0]).toContain('User creation started');
expect(infoLogs[0][1]).toEqual({ userData });
// Verify success log
expect(infoLogs[1][0]).toContain('User creation successful');
expect(infoLogs[1][1]).toEqual({ userId: 123 });
});
test('should output error log on error', async () => {
// Mock to cause error
const consoleSpy = jest.spyOn(console, 'error').mockImplementation();
try {
// Process that causes error
await userService.createUser({ name: '', email: 'invalid' });
} catch (error) {
// Verify error log
const errorLogs = mockLogger.getLogsByLevel('error');
expect(errorLogs).toHaveLength(1);
expect(errorLogs[0][0]).toContain('User creation error');
}
consoleSpy.mockRestore();
});
});