Skip to content

Conversation

@NathanFlurry
Copy link
Member

No description provided.

@vercel
Copy link

vercel bot commented Nov 4, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Preview Comments Updated (UTC)
rivetkit-serverless Ready Ready Preview Comment Nov 4, 2025 8:26pm
3 Skipped Deployments
Project Deployment Preview Comments Updated (UTC)
rivet-cloud Ignored Ignored Nov 4, 2025 8:26pm
rivet-inspector Ignored Ignored Preview Nov 4, 2025 8:26pm
rivet-site Ignored Ignored Preview Nov 4, 2025 8:26pm

This was referenced Nov 4, 2025
Copy link
Member Author

NathanFlurry commented Nov 4, 2025

Warning

This pull request is not mergeable via GitHub because a downstack PR is open. Once all requirements are satisfied, merge this PR as a stack on Graphite.
Learn more


How to use the Graphite Merge Queue

Add the label merge-queue to this PR to add it to the merge queue.

You must have a Graphite account in order to use the merge queue. Sign up using this link.

An organization admin has enabled the Graphite Merge Queue in this repository.

Please do not merge from GitHub as this will restart CI on PRs being processed by the merge queue.

This stack of pull requests is managed by Graphite. Learn more about stacking.

@claude
Copy link

claude bot commented Nov 4, 2025

Code Review - PR #3334: fix(rivetkit): fix not pino bindings

Summary

This PR fixes a bug where child logger bindings (contextual fields added via .child()) were not being included in the formatted log output. The fix correctly captures and includes these bindings in all log statements.

Positive Findings

Correct Bug Fix: The change properly addresses the issue by:

  • Using the method parameter (previously unused with _method) to access this context
  • Calling this.bindings() to retrieve child logger fields
  • Spreading bindings into the log output objects

Consistent Implementation: Bindings are correctly added to all 4 code paths in the logMethod hook

Proper Ordering: Bindings are spread first, then merged with log data, allowing log-specific fields to override bindings if needed

Issues & Recommendations

🔴 Critical: Type Safety Concern

Issue: The code uses (this as any).bindings?.() which bypasses TypeScript's type system.

rivetkit-typescript/packages/rivetkit/src/common/log.ts:167

const bindings = (this as any).bindings?.() || {};

Recommendation: Consider adding a proper type definition for the Pino logger context:

interface PinoLoggerContext {
  bindings(): Record<string, any>;
}

// Then use:
const bindings = (this as PinoLoggerContext).bindings?.() || {};

This would provide better type safety while still being explicit about the expected interface.

⚠️ Medium: Test Coverage

Issue: No test coverage for this bug fix or the logging functionality in general.

Impact:

  • Child logger bindings could regress without detection
  • The behavior is not documented through tests

Recommendation: Add tests that verify:

  1. Child logger bindings appear in formatted output
  2. Nested child loggers accumulate bindings correctly
  3. Log-specific fields can override bindings

Example test structure:

const logger = getLogger("test").child({ requestId: "123", userId: "abc" });
logger.info({ action: "login" }, "user logged in");
// Assert output contains: requestId=123, userId=abc, action=login, msg="user logged in"

📝 Minor: Documentation

Issue: The PR body is empty and doesn't explain what issue this fixes.

Recommendation: Add context about:

  • What bug was observed (bindings not appearing in logs)
  • How to reproduce the issue
  • How this fix resolves it

Performance Considerations

Minimal Impact: The bindings() call and object spread operations are lightweight. The fallback to empty object || {} prevents errors if bindings() is undefined.

Security Considerations

No Security Issues: The change doesn't introduce security concerns. Bindings are already trusted data from the application code.

Code Quality

The implementation follows the existing patterns in the codebase. The repetition across 4 code paths suggests potential for refactoring, but that would be outside the scope of this bug fix.

Conclusion

Verdict: ✅ Approve with recommendations

This is a valid bug fix that correctly implements the intended functionality. The main concerns are:

  1. Lack of type safety (use of any)
  2. Missing test coverage

These should be addressed to prevent regressions and improve code quality, but they don't block merging this fix.


Review generated by Claude Code

@claude
Copy link

claude bot commented Nov 4, 2025

Code Review: fix(rivetkit): fix not pino bindings

Summary

This PR fixes a bug where Pino logger bindings (child logger fields) were not being included in log output. The fix retrieves bindings from the logger instance and spreads them into the log output object.

Positive Aspects ✅

  1. Correct Fix: The change properly captures and includes bindings from child loggers, which was previously missing
  2. Consistent Implementation: Bindings are correctly spread into all 4 log output branches (2-arg object/string, 1-arg object/string)
  3. Backwards Compatible: The change is additive and doesn't break existing functionality
  4. Proper Spread Order: Bindings are spread first, then other properties, allowing explicit fields to override bindings if needed

Issues & Concerns 🔍

1. Type Safety (Medium Priority)

The code uses (this as any).bindings?.() which bypasses TypeScript's type checking:

const bindings = (this as any).bindings?.() || {};

Recommendation: Consider adding a proper type definition or interface for the Pino logger context to maintain type safety. If this is necessary due to Pino's internal API, add a comment explaining why the any cast is required.

2. Missing Tests (High Priority)

There are no tests for the logging functionality, which means:

  • This bug wasn't caught by tests
  • The fix isn't verified by tests
  • Future regressions are possible

Recommendation: Add tests that verify:

  • Child loggers with bindings output those fields in logs
  • Multiple levels of child loggers work correctly
  • Bindings are properly merged with explicit log fields
  • Edge cases (empty bindings, null bindings, etc.)

Example test case:

test('child logger bindings are included in output', () => {
  const logger = getLogger('test').child({ requestId: '123' });
  // Assert that logs contain requestId field
});

3. Performance Consideration (Low Priority)

The bindings function is called on every log statement. While Pino is designed for this, consider:

  • Is bindings() called even when the log level is filtered out?
  • Could bindings be cached in some scenarios?

Note: This is likely fine as-is since Pino's logMethod hook is designed this way, but worth monitoring.

4. Documentation (Low Priority)

The existing TODO comment mentions performance improvements with a custom transport target:

// TODO: This is a hack to not implement our own transport target. We can get better perf if we have our own transport target.

Recommendation: Consider updating this TODO or the inline comment to mention that bindings support was added and should be maintained in any future refactoring.

Code Quality 📝

Strengths:

  • Clean, readable code
  • Consistent formatting
  • Proper use of spread operator
  • Good fallback with || {}

Minor Suggestions:

  1. The method parameter was renamed from _method to method but is still unused. Consider either using it or keeping the underscore prefix to indicate intentionally unused parameter.

Security Concerns 🔒

No security issues identified. The change doesn't introduce any new attack vectors.

Testing Recommendations 🧪

Given the lack of existing tests for this module, I recommend:

  1. Unit tests for configureDefaultLogger and the logMethod hook
  2. Integration tests that verify child logger output format
  3. Regression test specifically for this bindings bug

Example test structure:

describe('Logger bindings', () => {
  it('includes child logger bindings in output', () => { /* ... */ });
  it('merges bindings with explicit log fields', () => { /* ... */ });
  it('handles nested child loggers', () => { /* ... */ });
});

Verdict ✨

Approval with recommendations: This is a solid bug fix that addresses a real issue. The implementation is correct and consistent. However, the lack of tests is concerning for a logging library that's central to observability.

Suggested action items:

  1. ✅ Merge this PR (the fix is correct and needed)
  2. 🔄 Follow up with tests for the logging module
  3. 📝 Consider adding type safety improvements
  4. 🔍 Consider documenting the bindings behavior for users

References

@claude
Copy link

claude bot commented Nov 4, 2025

Code Review

Summary

This PR fixes a bug where child logger bindings (fields added via .child()) were not being included in the logged output. The fix retrieves bindings from the logger instance using this.bindings?.() and merges them into the log output.

Positive Changes ✅

  1. Bug Fix: The core issue is correctly addressed - bindings from child loggers are now properly extracted and included in log output
  2. Consistent Application: The bindings are spread into all log paths (2-arg, 1-arg object, 1-arg string cases)
  3. Proper Ordering: Bindings are spread first, then log data, ensuring log-specific fields can override bindings if needed
  4. Parameter Fix: Changed _method to method (removing the underscore prefix), though the parameter still isn't used

Code Quality Observations

Type Safety Concern ⚠️

const bindings = (this as any).bindings?.() || {};

Using as any bypasses type safety. While this may be necessary due to Pino's type definitions, consider:

  • Adding a comment explaining why as any is needed
  • If bindings() is an official Pino API, consider creating a proper type definition

Repetitive Code 📝
The bindings spread pattern { ...bindings, ...objOrMsg, ... } is repeated 4 times. Consider:

const mergeWithBindings = (data: any) => ({ ...bindings, ...data });

// Then use:
customWrite(levelName, mergeWithBindings({ ...objOrMsg, msg, time }));

Empty Object Fallback 💭

const bindings = (this as any).bindings?.() || {};

Good defensive programming - ensures bindings is always an object even if the method doesn't exist.

Testing Concerns 🧪

No test files were found for the logging module. Consider adding tests to verify:

  • Child logger bindings are properly included in output
  • Bindings don't override explicit log fields inappropriately
  • Edge cases (undefined bindings, empty bindings, nested child loggers)

Performance Considerations ⚡

  • The bindings spread is done on every log call. This is likely acceptable, but if logging is high-volume, consider benchmarking
  • Object spreading is generally fast, but happens 4x per logMethod call due to code repetition

Security ✓

No security concerns identified. The bindings are treated the same as other log data.

Recommendations

  1. High Priority: Add inline comment explaining the as any cast and referencing Pino's API
  2. Medium Priority: Add tests for child logger functionality
  3. Low Priority: Refactor to reduce code duplication (helper function for merging bindings)
  4. Low Priority: Consider whether method parameter should be used (currently ignored)

Overall Assessment

Approved - This is a solid bug fix that addresses the issue correctly. The implementation is safe and follows the existing code patterns. The concerns raised are minor improvements that can be addressed in follow-up work if desired.

The fix properly handles the Pino child logger API and ensures structured logging context is preserved, which is important for log aggregation and debugging.

@claude
Copy link

claude bot commented Nov 4, 2025

Code Review: fix(rivetkit): fix pino bindings

Summary

This PR fixes a bug where child logger bindings (fields added via logger.child({ ... })) were not being included in the custom log output. The fix retrieves bindings from the Pino logger instance and merges them into all log entries.

Positive Aspects ✅

  1. Correct Fix: The implementation properly retrieves bindings using (this as any).bindings?.() with a fallback to an empty object, which is the correct way to access Pino's internal bindings.

  2. Consistent Application: The bindings are consistently spread into all four code paths (2-arg object, 2-arg non-object, 1-arg object, 1-arg non-object), ensuring child logger fields appear in all log entries.

  3. Proper Ordering: Bindings are spread first (...bindings) before other properties, allowing explicit log parameters to override binding values if needed - this is the correct precedence.

  4. Minimal Change: The fix is surgical and doesn't introduce unnecessary refactoring or complexity.

Observations & Suggestions 🔍

1. Type Safety Concern (Minor)

const bindings = (this as any).bindings?.() || {};

Issue: Using as any bypasses type checking. While this is sometimes necessary with Pino's internal APIs, it reduces type safety.

Suggestion: Consider creating a typed interface:

interface PinoLoggerInternal {
  bindings?: () => Record<string, unknown>;
}

const bindings = ((this as unknown) as PinoLoggerInternal).bindings?.() || {};

This provides minimal type documentation while maintaining flexibility.

2. Missing Unit Tests

Observation: This fix addresses a critical bug in logging functionality, but I don't see corresponding test coverage being added.

Recommendation: Add tests that verify:

  • Child logger bindings appear in log output
  • Nested child loggers work correctly
  • Explicit log parameters override binding values
  • Empty/undefined bindings are handled gracefully

Example test case structure:

test('child logger bindings appear in output', () => {
  const logger = getBaseLogger().child({ actorId: '123', target: 'test' });
  logger.info({ msg: 'test message' });
  // Assert output contains both actorId and target
});

3. Unused Parameter Issue (Fixed)

Good catch: Changing _method to method suggests the linter was complaining about an unused parameter. While method still isn't used in the function body, it's now available if needed for future enhancements.

4. Code Duplication (Pre-existing)

Observation: The four branches for handling different input argument patterns have significant duplication. The only differences are:

  • Whether msg is included
  • Whether the first arg is an object or needs string conversion

Suggestion for future PR: Consider refactoring to reduce duplication:

const logObject = { ...bindings, time };

if (inputArgs.length >= 2) {
  const [objOrMsg, msg] = inputArgs;
  if (typeof objOrMsg === "object" && objOrMsg !== null) {
    Object.assign(logObject, objOrMsg, { msg });
  } else {
    logObject.msg = String(objOrMsg);
  }
} else if (inputArgs.length === 1) {
  const [objOrMsg] = inputArgs;
  if (typeof objOrMsg === "object" && objOrMsg !== null) {
    Object.assign(logObject, objOrMsg);
  } else {
    logObject.msg = String(objOrMsg);
  }
}

customWrite(levelName, logObject);

This is purely a style suggestion and not blocking for this PR.

Performance Considerations ⚡

The changes have minimal performance impact:

  • bindings() call happens once per log statement
  • Object spreading is fast for small objects (typical binding count is <10 fields)
  • The ?. optional chaining adds negligible overhead

Security Considerations 🔒

No security concerns identified. The code:

  • Doesn't expose sensitive data beyond what's already being logged
  • Properly handles undefined/null values
  • Uses safe object spreading

Testing Recommendations 🧪

  1. Manual Testing: Verify that child loggers created in actor/instance.ts now correctly include actorId, deploymentId, etc. in their output
  2. Automated Tests: Add unit tests as mentioned above
  3. Integration Testing: Confirm that the logfmt output in production environments includes all expected fields

Conclusion

Verdict: ✅ LGTM with minor suggestions

This is a solid bug fix that addresses the core issue correctly. The implementation follows Pino's patterns and maintains backward compatibility. The suggestions above are for improving code quality and test coverage but are not blocking issues.

The main actionable item is adding test coverage to prevent regression of this bug in the future.


Reviewed by: Claude Code
Priority: The fix itself can merge as-is. Test coverage should be addressed in a follow-up PR if not included here.

@graphite-app
Copy link
Contributor

graphite-app bot commented Nov 4, 2025

Merge activity

  • Nov 4, 8:10 PM UTC: NathanFlurry added this pull request to the Graphite merge queue.
  • Nov 4, 8:11 PM UTC: CI is running for this pull request on a draft pull request (#3349) due to your merge queue CI optimization settings.
  • Nov 4, 8:41 PM UTC: The Graphite merge queue removed this pull request due to removal of a downstack PR #3342.
  • Nov 4, 8:53 PM UTC: NathanFlurry added this pull request to the Graphite merge queue.
  • Nov 4, 8:53 PM UTC: CI is running for this pull request on a draft pull request (#3353) due to your merge queue CI optimization settings.
  • Nov 4, 8:56 PM UTC: Merged by the Graphite merge queue via draft PR: #3353.

graphite-app bot pushed a commit that referenced this pull request Nov 4, 2025
@claude
Copy link

claude bot commented Nov 4, 2025

Code Review

Summary

This PR fixes a bug where Pino logger bindings (child logger fields) were not being included in the log output. The fix extracts bindings from the logger instance and spreads them into all log entries.

Positive Aspects ✅

  1. Bug Fix: The change correctly addresses the issue of missing bindings in child loggers. When using logger.child({ target: 'foo' }), the target field will now properly appear in logs.
  2. Consistent Implementation: The bindings are consistently spread across all code paths (2-arg, 1-arg object, and 1-arg string cases).
  3. Safe Access: Uses optional chaining and fallback for bindings access: (this as any).bindings?.() || {}

Code Quality 📝

Type Safety Concern
The use of (this as any) to access bindings is a workaround due to TypeScript's limitations with Pino's hook context. While functional, consider:

  • Adding a comment explaining why any is necessary here
  • Example: // Pino's hook context doesn't expose bindings in types, but it's available at runtime

Object Spread Order
The bindings are spread first, then the log object:

customWrite(levelName, {
    ...bindings,
    ...objOrMsg,
    msg,
    time,
});

This is correct because it allows explicit log properties to override bindings if there's a conflict, which is the expected behavior.

Performance Considerations ⚡

  • Minor overhead: Each log call now invokes bindings() function and performs object spreading
  • Impact: Negligible for typical logging volumes
  • Optimization opportunity: Could cache bindings result if performance becomes an issue, but current implementation is reasonable

Test Coverage 🧪

Missing: No tests were added for this functionality. Consider adding tests that verify:

  1. Child logger bindings appear in log output
  2. Explicit log properties override bindings when there's a name conflict
  3. Nested child loggers accumulate bindings correctly

Example test case:

const childLogger = logger.child({ target: 'test-target' });
childLogger.info({ custom: 'value' }, 'test message');
// Verify output contains both 'target' and 'custom' fields

Security Concerns 🔒

No security issues identified. The bindings are user-controlled (set via .child()) and already part of the logger context.

Recommendations 💡

  1. Add a code comment explaining the (this as any) type assertion
  2. Consider adding unit tests for child logger binding behavior
  3. Document this behavior in the logger's JSDoc if not already present

Verdict

Approved ✓ - This is a solid bug fix that correctly implements Pino bindings support. The implementation is consistent and safe. Adding tests would strengthen confidence in this change, but the code quality is good.

@claude
Copy link

claude bot commented Nov 4, 2025

Code Review

Summary

This PR fixes a critical bug where Pino child logger bindings (fields added via .child()) were not being propagated to the custom log output. The fix correctly retrieves and spreads bindings from the logger instance into all log messages.

Code Quality & Best Practices ✅

Strengths:

  • Correctly fixes the root issue: The change from _method to method and the addition of bindings retrieval properly addresses the missing child logger context
  • Consistent application: Bindings are spread in all code paths (2-arg, 1-arg object, 1-arg string cases)
  • Proper ordering: ...bindings is spread first, then the specific log data, allowing individual log calls to override binding values if needed
  • Type safety: Uses (this as any).bindings?.() with optional chaining to safely access Pino's internal API

Minor considerations:

  • The (this as any) cast is necessary but brittle - Pino's bindings() method is part of the internal API and could change in future versions
  • Consider adding a comment explaining why this.bindings() needs to be called in this context (it's accessing the Pino logger instance within the logMethod hook)

Potential Issues 🔍

Low Risk:

  1. API stability: The code relies on Pino's internal bindings() method. While this is a common pattern, it's not part of the public API. Consider:

    • Adding a test to verify bindings work as expected
    • Monitoring Pino release notes for changes to internal APIs
  2. Performance: The spread operator is called in every log invocation. For high-volume logging, this could have minor performance implications, but it's likely negligible and necessary for correctness.

Testing Recommendations 🧪

The PR lacks test coverage for the fixed functionality. Recommend adding tests such as:

import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import { configureDefaultLogger, getBaseLogger } from './log';

describe('child logger bindings', () => {
  let logOutput: string[] = [];
  const originalLog = console.log;

  beforeEach(() => {
    logOutput = [];
    console.log = (msg: string) => logOutput.push(msg);
  });

  afterEach(() => {
    console.log = originalLog;
  });

  it('should include child logger bindings in log output', async () => {
    await configureDefaultLogger('info');
    const logger = getBaseLogger();
    const child = logger.child({ requestId: '123', userId: 'abc' });
    
    child.info({ action: 'test' }, 'test message');
    
    const output = logOutput[0];
    expect(output).toContain('requestId=123');
    expect(output).toContain('userId=abc');
    expect(output).toContain('action=test');
    expect(output).toContain('msg="test message"');
  });

  it('should handle nested child loggers', async () => {
    await configureDefaultLogger('info');
    const logger = getBaseLogger();
    const child1 = logger.child({ layer: 'api' });
    const child2 = child1.child({ endpoint: '/users' });
    
    child2.info('processing request');
    
    const output = logOutput[0];
    expect(output).toContain('layer=api');
    expect(output).toContain('endpoint=/users');
  });
});

Security Considerations ✅

No security concerns identified. The change doesn't introduce any new attack surfaces.

Performance Considerations ⚡

  • Minimal impact: The spread operator adds negligible overhead
  • Positive impact: This fix actually improves observability, which can help identify performance issues faster

Impact Assessment

Files affected: 1 file, core logging infrastructure
Risk level: Low (fixes existing bug, doesn't change public API)
Breaking changes: None

This is a well-implemented bug fix that improves structured logging by ensuring child logger context is properly propagated. The implementation is clean and consistent.

Recommendation: ✅ Approve with suggestion to add tests

The code is ready to merge, but adding test coverage would prevent regression and document the expected behavior for future maintainers.

graphite-app bot pushed a commit that referenced this pull request Nov 4, 2025
@graphite-app graphite-app bot closed this Nov 4, 2025
@graphite-app graphite-app bot deleted the 11-04-fix_rivetkit_fix_not_pino_bindings branch November 4, 2025 20:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants