Package Exports
This package does not declare an exports field, so the exports above have been automatically detected and optimized by JSPM instead. If any package subpath is missing, it is recommended to post an issue to the original package (@heyatlas/logger) to support the "exports" field. If that is not possible, create a JSPM override to customize the exports field for this package.
Readme
@heyatlas/logger
A structured logger with Slack integration and AWS Lambda support.
Changelog
See CHANGELOG.md for a detailed list of changes.
Features
- Structured logging with Bunyan
- Slack integration
- AWS Lambda context support
- TypeScript support
- Log level management
- Async context tracking
- Context management with AsyncLocalStorage
Installation
npm install @heyatlas/loggerUsage
Basic Usage
import { Logger } from "@heyatlas/logger";
// Only name is required at root level
const logger = new Logger({
name: "my-app",
});
// Environment is determined by process.env.NODE_ENV
// Defaults to "local" if not set
logger.info("Hello world", { userId: "123" });Usage Scenarios
1. AWS Lambda Functions
import { AsyncLocalStorage } from "async_hooks";
import { Logger, withLoggerLambda } from "@heyatlas/logger";
// Create execution context
const executionContext = new AsyncLocalStorage();
// Create logger configuration
const loggerConfig = {
name: "my-lambda-service",
slackApiToken: process.env.SLACK_TOKEN,
production: {
streams: [{ level: "info", type: "stdout" }],
slack: {
defaultChannel: "#prod-alerts",
level: "error",
},
},
};
// Create logger instance
const logger = new Logger(loggerConfig);
// Lambda handler
export const handler = withLoggerLambda(
executionContext,
logger, // Pass the logger instance
async (event, context) => {
// The logger is automatically available in the execution context
const contextLogger = executionContext.getStore()?.logger;
contextLogger?.info("Processing event", { event });
try {
// Your handler logic here
const result = await processEvent(event);
contextLogger?.info("Event processed successfully", { result });
return result;
} catch (error) {
contextLogger?.error("Failed to process event", { error });
throw error;
}
}
);
// Alternative: Pass config directly to withLoggerLambda
export const alternativeHandler = withLoggerLambda(
executionContext,
loggerConfig, // Pass the config directly
async (event, context) => {
// Logger is created internally and available in context
const contextLogger = executionContext.getStore()?.logger;
contextLogger?.info("Processing with config-based logger");
// ... handler logic
}
);2. EC2/ECS API Service (Express)
import express from "express";
import { AsyncLocalStorage } from "async_hooks";
import { createLogger } from "@heyatlas/logger";
const app = express();
const executionContext = new AsyncLocalStorage();
// Create logger factory
const { logger, getLogger } = createLogger(executionContext, {
name: "my-api-service",
slackApiToken: process.env.SLACK_TOKEN,
production: {
streams: [{ level: "info", type: "stdout" }],
slack: {
defaultChannel: "#prod-alerts",
level: "error",
},
},
});
// Export getLogger for use in other modules
export { getLogger };
// Middleware to create context for each request
app.use((req, res, next) => {
executionContext.run({ logger }, () => next());
});
// Example route
app.get("/users/:id", async (req, res) => {
const logger = getLogger();
try {
logger.info("Fetching user", { userId: req.params.id });
const user = await userService.getUser(req.params.id);
logger.info("User fetched successfully", { user });
res.json(user);
} catch (error) {
logger.error("Failed to fetch user", { error });
res.status(500).json({ error: "Internal server error" });
}
});3. GraphQL Service
import { ApolloServer } from "apollo-server";
import { AsyncLocalStorage } from "async_hooks";
import { createLogger } from "@heyatlas/logger";
const executionContext = new AsyncLocalStorage();
// Create logger factory
const { logger, getLogger } = createLogger(executionContext, {
name: "my-graphql-service",
slackApiToken: process.env.SLACK_TOKEN,
production: {
streams: [{ level: "info", type: "stdout" }],
slack: {
defaultChannel: "#prod-alerts",
level: "error",
},
},
});
// Export getLogger for use in other modules
export { getLogger };
// Apollo Server context
const context = async ({ req }) => {
return executionContext.run({ logger }, () => ({ req }));
};
// Example resolver
const resolvers = {
Query: {
user: async (_, { id }, context) => {
const logger = getLogger();
try {
logger.info("Fetching user", { userId: id });
const user = await userService.getUser(id);
logger.info("User fetched successfully", { user });
return user;
} catch (error) {
logger.error("Failed to fetch user", { error });
throw error;
}
},
},
};
const server = new ApolloServer({
typeDefs,
resolvers,
context,
});Slack Integration
const logger = new Logger({
name: "my-app",
slackApiToken: "your-slack-token",
staging: {
streams: [{ type: "stdout", level: "info" }],
slack: {
defaultChannel: "#staging-logs",
level: "warn",
},
},
production: {
streams: [{ type: "stdout", level: "info" }],
slack: {
defaultChannel: "#production-logs",
level: "error",
},
},
});
// Regular logging with level validation
logger.error("Something went wrong", {
slack: { channel: "#alerts" },
error: new Error("Details here"),
});
// Direct Slack messaging without level validation
// Useful for sending notifications that don't need to go through the logger
logger.slackLogger?.sendDirect("#notifications", "Important notification");The sendDirect method allows you to send messages directly to Slack without any formatting or level validation. This is useful for:
- Sending simple notifications that don't need to be logged
- Reusing the SlackLogger instance in other parts of your application
- Sending plain text messages to specific channels
Environment-based Configuration
The logger uses NODE_ENV to determine which environment configuration to use:
import { Logger } from "@heyatlas/logger";
const logger = new Logger({
name: "my-service",
slackApiToken: process.env.SLACK_TOKEN, // Optional: enable Slack integration
// Environment-specific configurations
local: {
streams: [{ level: "debug", type: "stdout" }],
},
test: {
streams: [{ level: "fatal", type: "stdout" }],
},
staging: {
streams: [{ level: "info", type: "stdout" }],
slack: {
defaultChannel: "#staging-logs",
level: "warn",
},
},
production: {
streams: [{ level: "info", type: "stdout" }],
slack: {
defaultChannel: "#prod-alerts",
level: "error",
},
},
});Configuration Structure
The logger uses two levels of configuration:
Root Configuration:
name(required): Logger nameslackApiToken(optional): Slack API token for integration
Environment Configuration:
streams(required): Array of stream configurationsslack(optional): Slack settings per environmentdefaultChannel: Default Slack channellevel: Minimum level for Slack notifications
Child Loggers
The logger supports creating child loggers that inherit context from their parent while allowing for additional bound fields. This is particularly useful for tracking request-specific information across different parts of your application.
// Create a parent logger
const logger = new Logger(config);
// Create a child logger with request-specific fields
const requestLogger = logger.child({ reqId: "123" });
// The child logger inherits the parent's context
logger.setContext("userId", "456");
requestLogger.info("Processing request"); // Includes both reqId and userId
// Child loggers can have their own context
requestLogger.setContext("component", "auth");
requestLogger.info("Authenticating user"); // Includes reqId, userId, and component
// Child loggers maintain their context across async boundaries
const executionContext = new AsyncLocalStorage<LoggerContext>();
executionContext.run({ logger: requestLogger }, async () => {
// The logger maintains its context here
requestLogger.info("Inside async context");
});Context Inheritance
Child loggers inherit their parent's context and can override it with their own bound fields. The inheritance works as follows:
- Parent context is copied to the child
- Bound fields are merged with the parent's context
- Child-specific context can be added using
setContext - Context is maintained across async boundaries
API Documentation
Log Levels
The logger supports the following log levels in order of priority:
fatal: System is unusableerror: Error conditionswarn: Warning conditionsinfo: Informational messagesdebug: Debug-level messagestrace: Trace-level messages
License
MIT