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
24 changes: 12 additions & 12 deletions docs/references/config.md
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,10 @@ A path prefix handled by a proxy that is not seen by the server.

{{ config_options(["server", "root_path"]) }}

### Observability

Observability configuration comprising logging and tracing.

#### Logging

##### Level
Expand All @@ -205,7 +209,7 @@ Minimum level for log messages. Can be one of
- `"error"`
- `"critical"`

{{ config_options(["server", "logging", "level"]) }}
{{ config_options(["observability", "logging", "level"]) }}

##### As JSON

Expand All @@ -215,25 +219,21 @@ Whether log messages should be emitted as JSON objects instead a human-readable

Defaults to `false` in an interactive session.

{{ config_options(["server", "logging", "as_json"]) }}
{{ config_options(["observability", "logging", "as_json"]) }}

#### Tracing

##### Endpoint

[OpenTelemetry collector](https://opentelemetry.io/docs/collector/) endpoint.

{{ config_options(["server", "tracing", "endpoint"]) }}

##### As Logs
##### Span Processors

Whether traces should be emitted as part of the logs.
List of [span processors](https://opentelemetry.io/docs/concepts/signals/traces/#span-processors) configured as
[plugins](#plugins).

!!! note

Defaults to `true` in an interactive session if not [endpoint](#endpoint) is defined.
Defaults to exporting spans to the console using [ravnar.observability.StructlogSpanExporter][] in interactive
sessions and otherwise no span processing.

{{ config_options(["server", "tracing", "as_logs"]) }}
{{ config_options(["observability", "tracing", "span_processors"]) }}

### Security

Expand Down
2 changes: 2 additions & 0 deletions docs/references/python_api.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,5 @@
::: ravnar.agents

::: ravnar.authenticators

::: ravnar.observability
1 change: 0 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ dependencies = [
"jsonpatch>=1.33,<2",
"l2sl>=0.4,<0.5",
"opentelemetry-api>=1.39.0",
"opentelemetry-exporter-otlp>=1.39.0",
"opentelemetry-instrumentation-fastapi>=0.60b0",
"opentelemetry-instrumentation-sqlalchemy>=0.60b0",
"opentelemetry-sdk>=1.39.0",
Expand Down
55 changes: 39 additions & 16 deletions src/_ravnar/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,15 @@
from typing import Annotated, Any, Self, TypeVar

import l2sl
import opentelemetry.sdk.trace
from pydantic import AfterValidator, BaseModel, Field, field_validator, model_validator
from pydantic_settings import BaseSettings, PydanticBaseSettingsSource, SettingsConfigDict, YamlConfigSettingsSource
from upath import UPath

from _ravnar.agents import Agent
from _ravnar.authenticators import Authenticator
from _ravnar.utils import ImportStringWithParams, normalize_hostname, render_template

from .agents import Agent, DefaultAgent
from .authenticators import Authenticator

T = TypeVar("T")


Expand All @@ -39,22 +39,42 @@ def _render_templates(cls, data: Any) -> Any:
return render_template(data, context=dict(os.environ))


class ServerConfig(BaseModel, RenderableConfigMixin):
hostname: str = "127.0.0.1"
port: int = 8000
proxy_headers: bool = False
forwarded_allow_ips: list[str] = Field(default_factory=lambda: ["*"])
root_path: str = ""


class LoggingConfig(BaseModel, RenderableConfigMixin):
level: l2sl.LogLevel = l2sl.LogLevel("info")
as_json: bool = Field(default_factory=lambda: not interactive_session())


def default_tracing_span_processors() -> list[ImportStringWithParams[opentelemetry.sdk.trace.SpanProcessor]]:
if not interactive_session():
return []

from opentelemetry.sdk.trace.export import SimpleSpanProcessor

from ravnar.observability import StructlogSpanExporter

return [
ImportStringWithParams(
cls_or_fn=SimpleSpanProcessor,
params={"span_exporter": ImportStringWithParams(cls_or_fn=StructlogSpanExporter)},
)
]


class TracingConfig(BaseModel, RenderableConfigMixin):
endpoint: str | None = None
as_logs: bool = Field(default_factory=lambda values: interactive_session() and values["endpoint"] is None)
span_processors: list[ImportStringWithParams[opentelemetry.sdk.trace.SpanProcessor]] = Field(
default_factory=default_tracing_span_processors
)


class ServerConfig(BaseModel, RenderableConfigMixin):
hostname: str = "127.0.0.1"
port: int = 8000
proxy_headers: bool = False
forwarded_allow_ips: list[str] = Field(default_factory=lambda: ["*"])
root_path: str = ""
class ObservabilityConfig(BaseModel, RenderableConfigMixin):
logging: LoggingConfig = Field(default_factory=LoggingConfig)
tracing: TracingConfig = Field(default_factory=TracingConfig)

Expand Down Expand Up @@ -112,12 +132,14 @@ class DynamicAgentConfig(BaseModel, RenderableConfigMixin):
allowed_env_vars: Allowlist = Field(default_factory=list)


def default_static_agents() -> dict[str, ImportStringWithParams[Agent]]:
from ravnar.agents import DefaultAgent

return {"default": ImportStringWithParams(cls_or_fn=DefaultAgent)}


class AgentConfig(BaseModel, RenderableConfigMixin):
static: dict[str, ImportStringWithParams[Agent]] = Field(
default_factory=lambda: { # type: ignore[arg-type]
"default": ImportStringWithParams(cls_or_fn=DefaultAgent),
}
)
static: dict[str, ImportStringWithParams[Agent]] = Field(default_factory=default_static_agents)
dynamic: DynamicAgentConfig = Field(default_factory=DynamicAgentConfig)

@model_validator(mode="after")
Expand All @@ -129,6 +151,7 @@ def _ensure_not_agentless(self) -> Self:

class BaseConfig(BaseSettings, RenderableConfigMixin):
server: ServerConfig = Field(default_factory=ServerConfig)
observability: ObservabilityConfig = Field(default_factory=ObservabilityConfig)
security: SecurityConfig = Field(default_factory=SecurityConfig)
storage: StorageConfig = Field(default_factory=StorageConfig)

Expand Down
6 changes: 2 additions & 4 deletions src/_ravnar/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,9 @@
from opentelemetry import trace
from opentelemetry.instrumentation.fastapi import FastAPIInstrumentor

from _ravnar import schema
from _ravnar import observability, schema
from _ravnar.events import EventProcessor
from _ravnar.mixin import SetupTeardownMixin
from _ravnar.observability import configure_logging, configure_tracing
from _ravnar.security import SecurityHeadersMiddleware, User, make_authorized_user_factory
from _ravnar.utils import TemplateRenderError, as_awaitable

Expand All @@ -39,8 +38,7 @@ def __init__(self, config: BaseConfig | None = None) -> None:
if config is None:
config = Config.parse()

configure_logging(config)
configure_tracing(config)
observability.configure(config.observability)

self.config = config
self.app = self._make_app(config)
Expand Down
Loading
Loading