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
10 changes: 9 additions & 1 deletion compile_protos.sh
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
#!/usr/bin/env bash


#!/usr/bin/env bash

set -e

PROTO_ROOT="${PROTO_ROOT:-..}"


# https://buf.build/docs/installation

cp ../prefab-cloud/prefab.proto .
cp $PROTO_ROOT/prefab-cloud/prefab.proto .
buf generate
71 changes: 71 additions & 0 deletions examples/standard-logging/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
## Standard Logging Example

This example demonstrates how to use Reforge's `LoggerFilter` to dynamically control log levels for standard Python logging.

### Features

- Dynamic log level control through Reforge configuration
- No application restart required to change log levels
- Works with standard Python `logging` module
- Can target specific loggers by name using context-based rules

### Setup

1. Install dependencies:

```bash
poetry install --no-root
```

2. Set your Reforge SDK key (or use local-only mode as in the example):

```bash
export REFORGE_SDK_KEY=your-sdk-key-here
```

3. Create a log level config in your Reforge dashboard:
- Config key: `log-levels.default`
- Type: `LOG_LEVEL_V2`
- Default value: `INFO` (or your preferred level)

### Running the Example

```bash
poetry run python standard_logging_example.py
```

The example will log messages at all levels (DEBUG, INFO, WARNING, ERROR) every second.

### Dynamic Control

While the example is running, you can:

1. Change the log level in your Reforge dashboard
2. See the output change in real-time without restarting the application
3. Use context-based rules to set different levels for different loggers

For example, you could create rules like:

- Set `DEBUG` level for `reforge.python.*` loggers
- Set `ERROR` level for all other loggers
- Set `INFO` level during business hours, `DEBUG` level at night

### How It Works

The `LoggerFilter` integrates with Python's standard logging by:

1. Implementing the `logging.Filter` interface
2. Querying Reforge config for the log level on each log message
3. Returning `True` (allow) or `False` (block) based on the configured level
4. Using a context containing the logger name for targeted rules

### Advanced Usage

You can customize the logger name lookup by subclassing `LoggerFilter`:

```python
class CustomLoggerFilter(LoggerFilter):
def logger_name(self, record: logging.LogRecord) -> str:
# Custom logic to derive logger name
return record.name.replace("myapp", "mycompany")
```
14 changes: 14 additions & 0 deletions examples/standard-logging/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
[tool.poetry]
name = "standard-logging-example"
version = "0.1.0"
description = "Example demonstrating Reforge SDK with standard Python logging"
authors = ["Reforge"]
package-mode = false

[tool.poetry.dependencies]
python = "^3.9"
sdk-reforge = {path = "../../", develop = true}

[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"
69 changes: 69 additions & 0 deletions examples/standard-logging/standard_logging_example.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
"""
Example demonstrating dynamic log level control with standard Python logging.

This example shows how to use the LoggerFilter to dynamically control log levels
based on Reforge configuration. The log level can be changed in real-time through
the Reforge dashboard without restarting the application.
"""

import logging
import sys
import time
import os

from sdk_reforge import ReforgeSDK, Options, LoggerFilter


def main():
# Set up the root logger with a stdout handler
root_logger = logging.getLogger()
handler = logging.StreamHandler(sys.stdout)
handler.setFormatter(
logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
)
root_logger.addHandler(handler)
root_logger.setLevel(logging.DEBUG)

# Configure SDK to run in local-only mode for this example
# In production, you would set your REFORGE_SDK_KEY instead
os.environ["REFORGE_DATASOURCES"] = "LOCAL_ONLY"

def configure_logger():
"""Add the Reforge LoggerFilter after SDK is ready"""
handler.addFilter(LoggerFilter())
print("✓ Logger filter configured - log levels now controlled by Reforge\n")

# Create SDK with on_ready_callback to add the filter when ready
options = Options(
x_datafile="test.datafile.json", # In production, omit this for remote config
on_ready_callback=configure_logger,
logger_key="log-levels.default", # Config key that controls log levels
)

sdk = ReforgeSDK(options)

# Create a test logger
logger = logging.getLogger("reforge.python.example")

print("Logging at all levels every second...")
print("Try changing the 'log-levels.default' config in Reforge dashboard")
print("to see log output change dynamically!\n")

# Log messages at different levels in a loop
try:
for i in range(60): # Run for 60 seconds
logger.debug(f"[{i}] Debug message - only visible when level is DEBUG")
logger.info(f"[{i}] Info message - visible when level is INFO or below")
logger.warning(
f"[{i}] Warning message - visible when level is WARN or below"
)
logger.error(f"[{i}] Error message - visible when level is ERROR or below")
time.sleep(1)
except KeyboardInterrupt:
print("\nShutting down...")

sdk.close()


if __name__ == "__main__":
main()
91 changes: 91 additions & 0 deletions examples/structlog/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
## Structlog Example

This example demonstrates how to use Reforge's `LoggerProcessor` to dynamically control log levels for structlog.

### Features

- Dynamic log level control through Reforge configuration
- No application restart required to change log levels
- Works with structlog's processor pipeline
- Can target specific loggers by name using context-based rules
- Integrates seamlessly with structlog's structured logging features

### Setup

1. Install dependencies:

```bash
poetry install --no-root
```

2. Set your Reforge SDK key (or use local-only mode as in the example):

```bash
export REFORGE_SDK_KEY=your-sdk-key-here
```

3. Create a log level config in your Reforge dashboard:
- Config key: `log-levels.default`
- Type: `LOG_LEVEL_V2`
- Default value: `INFO` (or your preferred level)

### Running the Example

```bash
poetry run python structlog_example.py
```

The example will log messages at all levels (DEBUG, INFO, WARNING, ERROR) every second.

### Dynamic Control

While the example is running, you can:

1. Change the log level in your Reforge dashboard
2. See the output change in real-time without restarting the application
3. Use context-based rules to set different levels for different modules

For example, you could create rules like:

- Set `DEBUG` level for `myapp.database` module
- Set `ERROR` level for all other modules
- Set `INFO` level during business hours, `DEBUG` level at night

### How It Works

The `LoggerProcessor` integrates with structlog by:

1. Implementing a processor in the structlog pipeline
2. Querying Reforge config for the log level on each log event
3. Raising `DropEvent` to filter messages below the configured level
4. Using a context containing the logger name for targeted rules

**Important**: The `LoggerProcessor` must come **after** `structlog.stdlib.add_log_level` in the processor pipeline, as it depends on the level information added by that processor.

### Advanced Usage

You can customize the logger name lookup by subclassing `LoggerProcessor`:

```python
class CustomLoggerNameProcessor(LoggerProcessor):
def logger_name(self, logger, event_dict: dict) -> str:
# Use module name as logger name
return event_dict.get("module", "unknown")
```

This allows you to create log level rules based on module names, file names, or any other field in the event dictionary.

### Structlog Installation

Structlog is an optional dependency. To install the SDK with structlog support:

```bash
pip install sdk-reforge[structlog]
```

Or in your `pyproject.toml`:

```toml
[tool.poetry.dependencies]
sdk-reforge = {version = "^1.0", extras = ["structlog"]}
```
14 changes: 14 additions & 0 deletions examples/structlog/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
[tool.poetry]
name = "structlog-example"
version = "0.1.0"
description = "Example demonstrating Reforge SDK with structlog"
authors = ["Reforge"]
package-mode = false

[tool.poetry.dependencies]
python = "^3.9"
sdk-reforge = {path = "../../", develop = true, extras = ["structlog"]}

[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"
112 changes: 112 additions & 0 deletions examples/structlog/structlog_example.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
"""
Example demonstrating dynamic log level control with structlog.

This example shows how to use the LoggerProcessor to dynamically control log levels
based on Reforge configuration. The log level can be changed in real-time through
the Reforge dashboard without restarting the application.
"""

import time
import os
import structlog
from sdk_reforge import ReforgeSDK, Options, LoggerProcessor


class CustomLoggerNameProcessor(LoggerProcessor):
"""
Custom processor that extracts the logger name from the module field.

This demonstrates how to customize the logger name lookup for more
sophisticated log level rules based on module names.
"""

def logger_name(self, logger, event_dict: dict) -> str:
# Use the module name as the logger name for Reforge evaluation
return event_dict.get("module") or "unknown"


def main():
"""
Configure structlog with Reforge dynamic log level control.
"""
# Configure structlog with our processor
# Note: The LoggerProcessor must come after add_log_level
structlog.configure(
processors=[
structlog.processors.TimeStamper(fmt="iso"),
structlog.stdlib.add_log_level, # Must come before LoggerProcessor
structlog.processors.CallsiteParameterAdder(
{
structlog.processors.CallsiteParameter.THREAD_NAME,
structlog.processors.CallsiteParameter.FILENAME,
structlog.processors.CallsiteParameter.FUNC_NAME,
structlog.processors.CallsiteParameter.LINENO,
structlog.processors.CallsiteParameter.PROCESS,
structlog.processors.CallsiteParameter.MODULE,
}
),
CustomLoggerNameProcessor().processor, # Add Reforge log level control
structlog.dev.ConsoleRenderer(pad_event=25),
]
)

logger = structlog.getLogger()

# Configure SDK to run in local-only mode for this example
# In production, you would set your REFORGE_SDK_KEY instead
os.environ["REFORGE_DATASOURCES"] = "LOCAL_ONLY"

def on_ready():
"""Called when SDK is ready"""
logger.info("✓ SDK ready - log levels now controlled by Reforge")

# Create SDK
options = Options(
x_datafile="test.datafile.json", # In production, omit this for remote config
on_ready_callback=on_ready,
logger_key="log-levels.default", # Config key that controls log levels
)

sdk = ReforgeSDK(options)

logger.info("Starting structlog example")
logger.info(
"Try changing the 'log-levels.default' config in Reforge dashboard "
"to see log output change dynamically!"
)

# Log messages at different levels in a loop
try:
for i in range(60): # Run for 60 seconds
# Get a config value to show SDK is working
config_value = sdk.get("example-config", default="default-value")

logger.debug(
f"[{i}] Debug message",
config_value=config_value,
level_hint="Only visible when level is DEBUG",
)
logger.info(
f"[{i}] Info message",
config_value=config_value,
level_hint="Visible when level is INFO or below",
)
logger.warning(
f"[{i}] Warning message",
config_value=config_value,
level_hint="Visible when level is WARN or below",
)
logger.error(
f"[{i}] Error message",
config_value=config_value,
level_hint="Visible when level is ERROR or below",
)
time.sleep(1)
except KeyboardInterrupt:
logger.info("Shutting down...")

sdk.close()


if __name__ == "__main__":
main()
268 changes: 134 additions & 134 deletions prefab_pb2.py

Large diffs are not rendered by default.

Loading
Loading