Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,6 @@ node_modules
dist
.envrc
src/__tests__/temp-test.ts
.idea/
.claude/
.yarn/
21 changes: 21 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,26 @@
# Changelog

## 0.1.0 - 2025-11-04

### Features

- feat: add LOG_LEVEL_V2 with `getLogLevel()` method for dynamic log level control
- feat: add Pino logger integration (`createPinoLogger()`, `createPinoHook()`)
- feat: add Winston logger integration (`createWinstonLogger()`, `createWinstonFormat()`)
- feat: add `loggerKey` option to Reforge constructor (defaults to 'log-levels.default')
- feat: add ConfigType.LogLevelV2 enum value
- feat: support context-aware log level evaluation with `reforge-sdk-logging.logger-path`

### Fixes

- fix: add @types/node to devDependencies to satisfy ts-node peer dependency
- fix: replace @ts-ignore with @ts-expect-error for better type safety in integrations

### Documentation

- docs: add INTEGRATIONS.md with complete logger integration examples
- docs: update README.md with getLogLevel() usage and loggerKey option

## 0.0.7 - 2025-10-31

- fix: configs with duration would cause telemetry not to send
Expand Down
215 changes: 215 additions & 0 deletions INTEGRATIONS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,215 @@
# Logger Integrations

Reforge provides integrations with popular Node.js logging frameworks to enable dynamic log level control.

## Features

- **Dynamic Log Levels**: Change log levels in real-time without restarting your application
- **Optional Dependencies**: Logger integrations are optional peer dependencies
- **Flexible Version Support**: Compatible with pino >=7.0.0 and winston >=3.0.0

## Installation

Install Reforge along with your preferred logging framework:

```bash
# With Pino
npm install @reforge-com/node pino

# With Winston
npm install @reforge-com/node winston
```

## Pino Integration

### Basic Usage

```typescript
import { Reforge } from '@reforge-com/node';
import { createPinoLogger } from '@reforge-com/node/integrations/pino';

const reforge = new Reforge({
sdkKey: process.env.REFORGE_SDK_KEY,
loggerKey: 'my.log.config', // Config key for LOG_LEVEL_V2 (defaults to 'log-levels.default')
});

await reforge.init();

// Create a logger that dynamically gets its level from Reforge
const logger = await createPinoLogger(reforge, 'my.app.component', {
// Optional: any pino options
transport: { target: 'pino-pretty' }
});

if (logger) {
logger.info('Application started');
logger.debug('Debug information'); // Only logs if level permits
}
```

### Using with Existing Pino Logger

If you already have a Pino logger, you can use the `createPinoHook` to add Reforge log level information:

```typescript
import pino from 'pino';
import { createPinoHook } from '@reforge-com/node/integrations/pino';

const logger = pino({
mixin: createPinoHook(reforge, 'my.app.component')
});

// Log entries will include reforgeLogLevel field
logger.info('test'); // { reforgeLogLevel: 'info', msg: 'test', ... }
```

## Winston Integration

### Basic Usage

```typescript
import { Reforge } from '@reforge-com/node';
import { createWinstonLogger } from '@reforge-com/node/integrations/winston';

const reforge = new Reforge({
sdkKey: process.env.REFORGE_SDK_KEY,
loggerKey: 'my.log.config',
});

await reforge.init();

// Create a logger that dynamically gets its level from Reforge
const logger = await createWinstonLogger(reforge, 'my.app.component', {
// Optional: any winston options
transports: [new winston.transports.Console()]
});

if (logger) {
logger.info('Application started');
logger.debug('Debug information'); // Only logs if level permits
}
```

### Using with Existing Winston Logger

```typescript
import winston from 'winston';
import { createWinstonFormat } from '@reforge-com/node/integrations/winston';

const logger = winston.createLogger({
format: winston.format.combine(
await createWinstonFormat(reforge, 'my.app.component'),
winston.format.json()
),
transports: [new winston.transports.Console()]
});

// Log entries will include reforgeLogLevel field
logger.info('test'); // { reforgeLogLevel: 'info', message: 'test', ... }
```

## Configuring Log Levels in Reforge

### Create a LOG_LEVEL_V2 Config

1. In the Reforge UI, create a new config with type `LOG_LEVEL_V2`
2. Set your logger key (e.g., `my.log.config`)
3. Add rules based on the `reforge-sdk-logging.logger-path` context property

### Example Rules

```typescript
// Rule 1: DEBUG for specific component
{
criteria: [
{
propertyName: "reforge-sdk-logging.logger-path",
operator: "PROP_IS_ONE_OF",
valueToMatch: { stringList: { values: ["my.app.auth"] } }
}
],
value: { logLevel: "DEBUG" }
}

// Rule 2: INFO as default
{
criteria: [],
value: { logLevel: "INFO" }
}
```

### Using getLogLevel() Directly

You can also use `getLogLevel()` directly with any logging framework:

```typescript
import { Reforge, LogLevel } from '@reforge-com/node';

const reforge = new Reforge({
sdkKey: process.env.REFORGE_SDK_KEY,
loggerKey: 'my.log.config',
});

await reforge.init();

// Get the log level for a specific logger
const level = reforge.getLogLevel('my.app.component');

// Map to your logger's level system
if (level === LogLevel.Debug) {
myLogger.level = 'debug';
} else if (level === LogLevel.Info) {
myLogger.level = 'info';
}
// ... etc
```

## Log Level Mapping

### Reforge → Pino
- `TRACE` → `trace`
- `DEBUG` → `debug`
- `INFO` → `info`
- `WARN` → `warn`
- `ERROR` → `error`
- `FATAL` → `fatal`

### Reforge → Winston
- `TRACE` → `debug` (Winston doesn't have trace)
- `DEBUG` → `debug`
- `INFO` → `info`
- `WARN` → `warn`
- `ERROR` → `error`
- `FATAL` → `error` (Winston doesn't have fatal)

## Important Notes

1. **No Hierarchy Traversal**: Unlike the previous LOG_LEVEL implementation, LOG_LEVEL_V2 does NOT traverse logger name hierarchies. Each logger name is evaluated independently.

2. **Default Value**:
- The default `loggerKey` is `"log-levels.default"`
- If no config is found with that key, `getLogLevel()` returns `DEBUG`

3. **Dynamic Updates**: Log levels update automatically when your Reforge config changes (via SSE or polling).

4. **Context Evaluation**: The integrations pass the logger name in the context as:
```typescript
{
"reforge-sdk-logging": {
"lang": "javascript",
"logger-path": loggerName
}
}
```

## Graceful Degradation

If pino or winston is not installed, the integration functions will return `undefined` and log a warning to the console. Your application will continue to work, but dynamic log levels won't be available.

```typescript
const logger = await createPinoLogger(reforge, 'my.app');
if (!logger) {
console.warn('Pino not available, falling back to console');
// Use console or another fallback
}
```
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

Reforge Node.js client

📚 **[Full Documentation](https://docs.reforge.com/docs/sdks/node)**

---

Install the client
Expand Down Expand Up @@ -33,6 +35,7 @@ After the init completes you can use
- `reforge.get('some.config.name')` returns a raw value
- `reforge.isFeatureEnabled('some.feature.name')` returns true or false
- `reforge.shouldLog({loggerName, desiredLevel, defaultLevel, contexts})` returns true or false
- `reforge.getLogLevel(loggerName)` returns the configured log level for a logger name

Reforge supports [context](https://docs.prefab.cloud/docs/explanations/concepts/context) for
intelligent rule-based evaluation of `get` and `isFeatureEnabled` based on the current
Expand Down Expand Up @@ -91,6 +94,15 @@ Note that you can also provide Context as an object instead of a Map, e.g.:
}
```

## Logger Integrations

Reforge provides optional integrations with popular Node.js logging frameworks (Pino, Winston) for dynamic log level control.

See [INTEGRATIONS.md](INTEGRATIONS.md) for detailed documentation on:
- Setting up Pino or Winston with dynamic log levels
- Using `getLogLevel()` with any logging framework
- Configuration and examples

#### Option Definitions

Besides `apiKey`, you can initialize `new Reforge(...)` with the following options
Expand All @@ -101,6 +113,7 @@ Besides `apiKey`, you can initialize `new Reforge(...)` with the following optio
| collectLoggerCounts | Send counts of logger usage back to Reforge to power log-levels configuration screen | true |
| contextUploadMode | Upload either context "shapes" (the names and data types your app uses in reforge contexts) or periodically send full example contexts | "periodicExample" |
| defaultLevel | Level to be used as the min-verbosity for a `loggerPath` if no value is configured in Reforge | "warn" |
| loggerKey | Config key for LOG_LEVEL_V2 to use with `getLogLevel()` method | "log-levels.default" |
| enableSSE | Whether or not we should listen for live changes from Reforge | true |
| enablePolling | Whether or not we should poll for changes from Reforge | false |

Expand Down
30 changes: 29 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"packageManager": "yarn@4.9.2",
"name": "@reforge-com/node",
"version": "0.0.7",
"version": "0.1.0",
"description": "Feature Flags, Live Config, and Dynamic Log Levels",
"main": "dist/reforge.cjs",
"types": "dist/reforge.d.ts",
Expand All @@ -18,6 +18,21 @@
".": {
"import": "./dist/reforge.mjs",
"require": "./dist/reforge.cjs"
},
"./integrations/pino": {
"import": "./dist/integrations/pino.mjs",
"require": "./dist/integrations/pino.cjs",
"types": "./dist/integrations/pino.d.ts"
},
"./integrations/winston": {
"import": "./dist/integrations/winston.mjs",
"require": "./dist/integrations/winston.cjs",
"types": "./dist/integrations/winston.d.ts"
},
"./integrations": {
"import": "./dist/integrations/index.mjs",
"require": "./dist/integrations/index.cjs",
"types": "./dist/integrations/index.d.ts"
}
},
"files": [
Expand All @@ -37,6 +52,7 @@
"license": "ISC",
"devDependencies": {
"@types/jest": "^29.5.1",
"@types/node": "^24.10.0",
"@types/node-forge": "^1",
"@types/yaml": "^1.9.7",
"@typescript-eslint/eslint-plugin": "^5.59.2",
Expand All @@ -59,5 +75,17 @@
"eventsource": "^4.0.0",
"murmurhash": "^2.0.1",
"node-forge": "^1.3.1"
},
"peerDependencies": {
"pino": ">=7.0.0",
"winston": ">=3.0.0"
},
"peerDependenciesMeta": {
"pino": {
"optional": true
},
"winston": {
"optional": true
}
}
}
Loading