flock-core 0.2.4__py3-none-any.whl → 0.2.5__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of flock-core might be problematic. Click here for more details.
- flock/__init__.py +3 -0
- flock/config.py +31 -13
- flock/core/flock.py +10 -1
- flock/core/logging/logging.py +18 -19
- flock/core/logging/span_middleware/baggage_span_processor.py +31 -0
- flock/core/logging/telemetry.py +62 -34
- flock/core/logging/telemetry_exporter/base_exporter.py +38 -0
- flock/core/logging/telemetry_exporter/file_exporter.py +85 -0
- flock/core/logging/telemetry_exporter/sqlite_exporter.py +103 -0
- flock/core/logging/trace_and_logged.py +5 -1
- flock/platform/docker_tools.py +49 -0
- flock/platform/jaeger_install.py +86 -0
- {flock_core-0.2.4.dist-info → flock_core-0.2.5.dist-info}/METADATA +3 -1
- {flock_core-0.2.4.dist-info → flock_core-0.2.5.dist-info}/RECORD +17 -12
- flock_core-0.2.5.dist-info/entry_points.txt +2 -0
- flock/core/logging/telemetry_exporter/file_span.py +0 -37
- flock/core/logging/telemetry_exporter/sqllite_span.py +0 -68
- {flock_core-0.2.4.dist-info → flock_core-0.2.5.dist-info}/WHEEL +0 -0
- {flock_core-0.2.4.dist-info → flock_core-0.2.5.dist-info}/licenses/LICENSE +0 -0
flock/__init__.py
CHANGED
flock/config.py
CHANGED
|
@@ -1,29 +1,47 @@
|
|
|
1
1
|
# flock/config.py
|
|
2
|
-
import
|
|
2
|
+
from decouple import config
|
|
3
3
|
|
|
4
4
|
from flock.core.logging.telemetry import TelemetryConfig
|
|
5
5
|
|
|
6
6
|
# -- Connection and External Service Configurations --
|
|
7
|
-
TEMPORAL_SERVER_URL =
|
|
8
|
-
DEFAULT_MODEL =
|
|
7
|
+
TEMPORAL_SERVER_URL = config("TEMPORAL_SERVER_URL", "localhost:7233")
|
|
8
|
+
DEFAULT_MODEL = config("DEFAULT_MODEL", "openai/gpt-4o")
|
|
9
|
+
|
|
9
10
|
|
|
10
11
|
# API Keys and related settings
|
|
11
|
-
TAVILY_API_KEY =
|
|
12
|
-
GITHUB_PAT =
|
|
13
|
-
GITHUB_REPO =
|
|
14
|
-
GITHUB_USERNAME =
|
|
12
|
+
TAVILY_API_KEY = config("TAVILY_API_KEY", "")
|
|
13
|
+
GITHUB_PAT = config("GITHUB_PAT", "")
|
|
14
|
+
GITHUB_REPO = config("GITHUB_REPO", "")
|
|
15
|
+
GITHUB_USERNAME = config("GITHUB_USERNAME", "")
|
|
15
16
|
|
|
16
17
|
# -- Debugging and Logging Configurations --
|
|
17
|
-
LOCAL_DEBUG =
|
|
18
|
-
LOG_LEVEL =
|
|
18
|
+
LOCAL_DEBUG = config("LOCAL_DEBUG", True)
|
|
19
|
+
LOG_LEVEL = config("LOG_LEVEL", "DEBUG")
|
|
20
|
+
LOGGING_DIR = config("LOGGING_DIR", "logs")
|
|
19
21
|
|
|
20
|
-
|
|
21
|
-
JAEGER_ENDPOINT =
|
|
22
|
+
OTEL_SERVICE_NAME = config("OTL_SERVICE_NAME", "otel-flock")
|
|
23
|
+
JAEGER_ENDPOINT = config(
|
|
22
24
|
"JAEGER_ENDPOINT", "http://localhost:14268/api/traces"
|
|
23
25
|
) # Default gRPC endpoint for Jaeger
|
|
24
|
-
JAEGER_TRANSPORT =
|
|
26
|
+
JAEGER_TRANSPORT = config(
|
|
25
27
|
"JAEGER_TRANSPORT", "http"
|
|
26
28
|
).lower() # Options: "grpc" or "http"
|
|
29
|
+
OTEL_SQL_DATABASE_NAME = config("OTEL_SQL_DATABASE", "flock_events.db")
|
|
30
|
+
OTEL_FILE_NAME = config("OTEL_FILE_NAME", "flock_events.jsonl")
|
|
31
|
+
OTEL_ENABLE_SQL = config("OTEL_ENABLE_SQL", True)
|
|
32
|
+
OTEL_ENABLE_FILE = config("OTEL_ENABLE_FILE", True)
|
|
33
|
+
OTEL_ENABLE_JAEGER = config("OTEL_ENABLE_JAEGER", True)
|
|
27
34
|
|
|
28
35
|
|
|
29
|
-
TELEMETRY = TelemetryConfig(
|
|
36
|
+
TELEMETRY = TelemetryConfig(
|
|
37
|
+
OTEL_SERVICE_NAME,
|
|
38
|
+
JAEGER_ENDPOINT,
|
|
39
|
+
JAEGER_TRANSPORT,
|
|
40
|
+
LOGGING_DIR,
|
|
41
|
+
OTEL_FILE_NAME,
|
|
42
|
+
OTEL_SQL_DATABASE_NAME,
|
|
43
|
+
OTEL_ENABLE_JAEGER,
|
|
44
|
+
OTEL_ENABLE_FILE,
|
|
45
|
+
OTEL_ENABLE_SQL,
|
|
46
|
+
)
|
|
47
|
+
TELEMETRY.setup_tracing()
|
flock/core/flock.py
CHANGED
|
@@ -5,7 +5,7 @@ import uuid
|
|
|
5
5
|
from typing import TypeVar
|
|
6
6
|
|
|
7
7
|
from opentelemetry import trace
|
|
8
|
-
from
|
|
8
|
+
from opentelemetry.baggage import get_baggage, set_baggage
|
|
9
9
|
|
|
10
10
|
from flock.core.context.context import FlockContext
|
|
11
11
|
from flock.core.context.context_manager import initialize_context
|
|
@@ -63,6 +63,11 @@ class Flock:
|
|
|
63
63
|
enable_logging=enable_logging,
|
|
64
64
|
)
|
|
65
65
|
logger.enable_logging = enable_logging
|
|
66
|
+
session_id = get_baggage("session_id")
|
|
67
|
+
if not session_id:
|
|
68
|
+
session_id = str(uuid.uuid4())
|
|
69
|
+
set_baggage("session_id", session_id)
|
|
70
|
+
span.set_attribute("session_id", get_baggage("session_id"))
|
|
66
71
|
|
|
67
72
|
display_banner()
|
|
68
73
|
|
|
@@ -193,12 +198,16 @@ class Flock:
|
|
|
193
198
|
run_id = f"{start_agent.name}_{uuid.uuid4().hex[:4]}"
|
|
194
199
|
logger.debug("Generated run ID", run_id=run_id)
|
|
195
200
|
|
|
201
|
+
set_baggage("run_id", run_id)
|
|
202
|
+
|
|
196
203
|
# TODO - Add a check for required input keys
|
|
197
204
|
input_keys = top_level_to_keys(start_agent.input)
|
|
198
205
|
for key in input_keys:
|
|
199
206
|
if key.startswith("flock."):
|
|
200
207
|
key = key[6:] # Remove the "flock." prefix
|
|
201
208
|
if key not in input:
|
|
209
|
+
from rich.prompt import Prompt
|
|
210
|
+
|
|
202
211
|
input[key] = Prompt.ask(
|
|
203
212
|
f"Please enter {key} for {start_agent.name}"
|
|
204
213
|
)
|
flock/core/logging/logging.py
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
# File: src/flock/core/logging.py
|
|
2
|
-
"""A unified logging module for Flock that works both in local/worker contexts
|
|
3
|
-
and inside Temporal workflows.
|
|
2
|
+
"""A unified logging module for Flock that works both in local/worker contexts and inside Temporal workflows.
|
|
4
3
|
|
|
5
4
|
Key points:
|
|
6
5
|
- We always have Temporal imported, so we cannot decide based on import.
|
|
@@ -24,6 +23,7 @@ with workflow.unsafe.imports_passed_through():
|
|
|
24
23
|
|
|
25
24
|
def in_workflow_context() -> bool:
|
|
26
25
|
"""Returns True if this code is running inside a Temporal workflow context.
|
|
26
|
+
|
|
27
27
|
It does this by attempting to call workflow.info() and returning True
|
|
28
28
|
if successful. Otherwise, it returns False.
|
|
29
29
|
"""
|
|
@@ -31,10 +31,7 @@ def in_workflow_context() -> bool:
|
|
|
31
31
|
workflow.logger.debug("Checking if in workflow context...")
|
|
32
32
|
# loguru_logger.debug("Checking if in workflow context...")
|
|
33
33
|
# This call will succeed only if we're in a workflow context.
|
|
34
|
-
|
|
35
|
-
return True
|
|
36
|
-
else:
|
|
37
|
-
return False
|
|
34
|
+
return bool(hasattr(workflow.info(), "is_replaying"))
|
|
38
35
|
except Exception:
|
|
39
36
|
return False
|
|
40
37
|
|
|
@@ -67,22 +64,24 @@ loguru_logger.add(
|
|
|
67
64
|
|
|
68
65
|
# Define a dummy logger that does nothing
|
|
69
66
|
class DummyLogger:
|
|
70
|
-
|
|
67
|
+
"""A dummy logger that does nothing when called."""
|
|
68
|
+
|
|
69
|
+
def debug(self, *args, **kwargs): # noqa: D102
|
|
71
70
|
pass
|
|
72
71
|
|
|
73
|
-
def info(self, *args, **kwargs):
|
|
72
|
+
def info(self, *args, **kwargs): # noqa: D102
|
|
74
73
|
pass
|
|
75
74
|
|
|
76
|
-
def warning(self, *args, **kwargs):
|
|
75
|
+
def warning(self, *args, **kwargs): # noqa: D102
|
|
77
76
|
pass
|
|
78
77
|
|
|
79
|
-
def error(self, *args, **kwargs):
|
|
78
|
+
def error(self, *args, **kwargs): # noqa: D102
|
|
80
79
|
pass
|
|
81
80
|
|
|
82
|
-
def exception(self, *args, **kwargs):
|
|
81
|
+
def exception(self, *args, **kwargs): # noqa: D102
|
|
83
82
|
pass
|
|
84
83
|
|
|
85
|
-
def success(self, *args, **kwargs):
|
|
84
|
+
def success(self, *args, **kwargs): # noqa: D102
|
|
86
85
|
pass
|
|
87
86
|
|
|
88
87
|
|
|
@@ -92,7 +91,7 @@ dummy_logger = DummyLogger()
|
|
|
92
91
|
class FlockLogger:
|
|
93
92
|
"""A unified logger that selects the appropriate logging mechanism based on context.
|
|
94
93
|
|
|
95
|
-
- If running in a workflow context, it uses Temporal's built
|
|
94
|
+
- If running in a workflow context, it uses Temporal's built-in logger.
|
|
96
95
|
Additionally, if workflow.info().is_replaying is True, it suppresses debug/info/warning logs.
|
|
97
96
|
- Otherwise, it uses Loguru.
|
|
98
97
|
"""
|
|
@@ -114,22 +113,22 @@ class FlockLogger:
|
|
|
114
113
|
trace_id=get_current_trace_id(),
|
|
115
114
|
)
|
|
116
115
|
|
|
117
|
-
def debug(self, message: str, *args, **kwargs):
|
|
116
|
+
def debug(self, message: str, *args, **kwargs): # noqa: D102
|
|
118
117
|
self._get_logger().debug(message, *args, **kwargs)
|
|
119
118
|
|
|
120
|
-
def info(self, message: str, *args, **kwargs):
|
|
119
|
+
def info(self, message: str, *args, **kwargs): # noqa: D102
|
|
121
120
|
self._get_logger().info(message, *args, **kwargs)
|
|
122
121
|
|
|
123
|
-
def warning(self, message: str, *args, **kwargs):
|
|
122
|
+
def warning(self, message: str, *args, **kwargs): # noqa: D102
|
|
124
123
|
self._get_logger().warning(message, *args, **kwargs)
|
|
125
124
|
|
|
126
|
-
def error(self, message: str, *args, **kwargs):
|
|
125
|
+
def error(self, message: str, *args, **kwargs): # noqa: D102
|
|
127
126
|
self._get_logger().error(message, *args, **kwargs)
|
|
128
127
|
|
|
129
|
-
def exception(self, message: str, *args, **kwargs):
|
|
128
|
+
def exception(self, message: str, *args, **kwargs): # noqa: D102
|
|
130
129
|
self._get_logger().exception(message, *args, **kwargs)
|
|
131
130
|
|
|
132
|
-
def success(self, message: str, *args, **kwargs):
|
|
131
|
+
def success(self, message: str, *args, **kwargs): # noqa: D102
|
|
133
132
|
self._get_logger().success(message, *args, **kwargs)
|
|
134
133
|
|
|
135
134
|
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
from opentelemetry.baggage import get_baggage
|
|
2
|
+
from opentelemetry.sdk.trace import SpanProcessor
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class BaggageAttributeSpanProcessor(SpanProcessor):
|
|
6
|
+
"""A custom span processor that, on span start, inspects the baggage items from the parent context
|
|
7
|
+
and attaches specified baggage keys as attributes on the span.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
def __init__(self, baggage_keys=None):
|
|
11
|
+
# baggage_keys: list of baggage keys to attach to spans (e.g. ["session_id", "run_id"])
|
|
12
|
+
if baggage_keys is None:
|
|
13
|
+
baggage_keys = []
|
|
14
|
+
self.baggage_keys = baggage_keys
|
|
15
|
+
|
|
16
|
+
def on_start(self, span, parent_context):
|
|
17
|
+
# For each desired key, look up its value in the parent context baggage and set it as an attribute.
|
|
18
|
+
for key in self.baggage_keys:
|
|
19
|
+
value = get_baggage(key, context=parent_context)
|
|
20
|
+
if value is not None:
|
|
21
|
+
span.set_attribute(key, value)
|
|
22
|
+
|
|
23
|
+
def on_end(self, span):
|
|
24
|
+
# No action required on span end for this processor.
|
|
25
|
+
pass
|
|
26
|
+
|
|
27
|
+
def shutdown(self):
|
|
28
|
+
pass
|
|
29
|
+
|
|
30
|
+
def force_flush(self, timeout_millis: int = 30000):
|
|
31
|
+
pass
|
flock/core/logging/telemetry.py
CHANGED
|
@@ -1,17 +1,25 @@
|
|
|
1
1
|
"""This module sets up OpenTelemetry tracing for a service."""
|
|
2
2
|
|
|
3
|
+
import sys
|
|
4
|
+
|
|
3
5
|
from opentelemetry import trace
|
|
4
6
|
from opentelemetry.sdk.resources import Resource
|
|
5
7
|
from opentelemetry.sdk.trace import TracerProvider
|
|
6
|
-
from opentelemetry.sdk.trace.export import
|
|
7
|
-
|
|
8
|
-
)
|
|
8
|
+
from opentelemetry.sdk.trace.export import SimpleSpanProcessor
|
|
9
|
+
from temporalio import workflow
|
|
9
10
|
|
|
10
|
-
from flock.core.logging.
|
|
11
|
-
|
|
12
|
-
SQLiteSpanExporter,
|
|
11
|
+
from flock.core.logging.span_middleware.baggage_span_processor import (
|
|
12
|
+
BaggageAttributeSpanProcessor,
|
|
13
13
|
)
|
|
14
14
|
|
|
15
|
+
with workflow.unsafe.imports_passed_through():
|
|
16
|
+
from flock.core.logging.telemetry_exporter.file_exporter import (
|
|
17
|
+
FileSpanExporter,
|
|
18
|
+
)
|
|
19
|
+
from flock.core.logging.telemetry_exporter.sqlite_exporter import (
|
|
20
|
+
SqliteTelemetryExporter,
|
|
21
|
+
)
|
|
22
|
+
|
|
15
23
|
|
|
16
24
|
class TelemetryConfig:
|
|
17
25
|
"""This configuration class sets up OpenTelemetry tracing.
|
|
@@ -26,11 +34,15 @@ class TelemetryConfig:
|
|
|
26
34
|
def __init__(
|
|
27
35
|
self,
|
|
28
36
|
service_name: str,
|
|
29
|
-
jaeger_endpoint: str = None,
|
|
37
|
+
jaeger_endpoint: str | None = None,
|
|
30
38
|
jaeger_transport: str = "grpc",
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
39
|
+
local_logging_dir: str | None = None,
|
|
40
|
+
file_export_name: str | None = None,
|
|
41
|
+
sqlite_db_name: str | None = None,
|
|
42
|
+
enable_jaeger: bool = True,
|
|
43
|
+
enable_file: bool = True,
|
|
44
|
+
enable_sql: bool = True,
|
|
45
|
+
batch_processor_options: dict | None = None,
|
|
34
46
|
):
|
|
35
47
|
""":param service_name: Name of your service.
|
|
36
48
|
|
|
@@ -42,11 +54,16 @@ class TelemetryConfig:
|
|
|
42
54
|
self.service_name = service_name
|
|
43
55
|
self.jaeger_endpoint = jaeger_endpoint
|
|
44
56
|
self.jaeger_transport = jaeger_transport
|
|
45
|
-
self.
|
|
46
|
-
self.
|
|
57
|
+
self.file_export_name = file_export_name
|
|
58
|
+
self.sqlite_db_name = sqlite_db_name
|
|
59
|
+
self.local_logging_dir = local_logging_dir
|
|
47
60
|
self.batch_processor_options = batch_processor_options or {}
|
|
61
|
+
self.enable_jaeger = enable_jaeger
|
|
62
|
+
self.enable_file = enable_file
|
|
63
|
+
self.enable_sql = enable_sql
|
|
48
64
|
|
|
49
65
|
def setup_tracing(self):
|
|
66
|
+
"""Set up OpenTelemetry tracing with the specified exporters."""
|
|
50
67
|
# Create a Resource with the service name.
|
|
51
68
|
resource = Resource(attributes={"service.name": self.service_name})
|
|
52
69
|
provider = TracerProvider(resource=resource)
|
|
@@ -55,8 +72,8 @@ class TelemetryConfig:
|
|
|
55
72
|
# List to collect our span processors.
|
|
56
73
|
span_processors = []
|
|
57
74
|
|
|
58
|
-
# If a Jaeger endpoint is specified, add the Jaeger
|
|
59
|
-
if self.jaeger_endpoint:
|
|
75
|
+
# If a Jaeger endpoint is specified, add the Jaeger exporter.
|
|
76
|
+
if self.jaeger_endpoint and self.enable_jaeger:
|
|
60
77
|
if self.jaeger_transport == "grpc":
|
|
61
78
|
from opentelemetry.exporter.jaeger.proto.grpc import (
|
|
62
79
|
JaegerExporter,
|
|
@@ -77,33 +94,44 @@ class TelemetryConfig:
|
|
|
77
94
|
"Invalid JAEGER_TRANSPORT specified. Use 'grpc' or 'http'."
|
|
78
95
|
)
|
|
79
96
|
|
|
80
|
-
span_processors.append(
|
|
81
|
-
BatchSpanProcessor(
|
|
82
|
-
jaeger_exporter, **self.batch_processor_options
|
|
83
|
-
)
|
|
84
|
-
)
|
|
97
|
+
span_processors.append(SimpleSpanProcessor(jaeger_exporter))
|
|
85
98
|
|
|
86
99
|
# If a file path is provided, add the custom file exporter.
|
|
87
|
-
if self.
|
|
88
|
-
file_exporter = FileSpanExporter(
|
|
89
|
-
|
|
90
|
-
BatchSpanProcessor(
|
|
91
|
-
file_exporter, **self.batch_processor_options
|
|
92
|
-
)
|
|
100
|
+
if self.file_export_name and self.enable_file:
|
|
101
|
+
file_exporter = FileSpanExporter(
|
|
102
|
+
self.local_logging_dir, self.file_export_name
|
|
93
103
|
)
|
|
104
|
+
span_processors.append(SimpleSpanProcessor(file_exporter))
|
|
94
105
|
|
|
95
|
-
# If a SQLite database path is provided, add the
|
|
96
|
-
if self.
|
|
97
|
-
sqlite_exporter =
|
|
98
|
-
|
|
99
|
-
BatchSpanProcessor(
|
|
100
|
-
sqlite_exporter, **self.batch_processor_options
|
|
101
|
-
)
|
|
106
|
+
# If a SQLite database path is provided, ensure the DB exists and add the SQLite exporter.
|
|
107
|
+
if self.sqlite_db_name and self.enable_sql:
|
|
108
|
+
sqlite_exporter = SqliteTelemetryExporter(
|
|
109
|
+
self.local_logging_dir, self.sqlite_db_name
|
|
102
110
|
)
|
|
111
|
+
span_processors.append(SimpleSpanProcessor(sqlite_exporter))
|
|
103
112
|
|
|
104
113
|
# Register all span processors with the provider.
|
|
105
114
|
for processor in span_processors:
|
|
106
115
|
provider.add_span_processor(processor)
|
|
107
116
|
|
|
108
|
-
|
|
109
|
-
|
|
117
|
+
provider.add_span_processor(
|
|
118
|
+
BaggageAttributeSpanProcessor(baggage_keys=["session_id", "run_id"])
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
sys.excepthook = self.log_exception_to_otel
|
|
122
|
+
|
|
123
|
+
def log_exception_to_otel(self, exc_type, exc_value, exc_traceback):
|
|
124
|
+
"""Log unhandled exceptions to OpenTelemetry."""
|
|
125
|
+
if issubclass(exc_type, KeyboardInterrupt):
|
|
126
|
+
# Allow normal handling of KeyboardInterrupt
|
|
127
|
+
sys.__excepthook__(exc_type, exc_value, exc_traceback)
|
|
128
|
+
return
|
|
129
|
+
|
|
130
|
+
# Use OpenTelemetry to record the exception
|
|
131
|
+
with self.global_tracer.start_as_current_span(
|
|
132
|
+
"UnhandledException"
|
|
133
|
+
) as span:
|
|
134
|
+
span.record_exception(exc_value)
|
|
135
|
+
span.set_status(
|
|
136
|
+
trace.Status(trace.StatusCode.ERROR, str(exc_value))
|
|
137
|
+
)
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"""Base class for custom OpenTelemetry exporters."""
|
|
2
|
+
|
|
3
|
+
from abc import ABC, abstractmethod
|
|
4
|
+
|
|
5
|
+
from opentelemetry.sdk.trace.export import SpanExporter, SpanExportResult
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class TelemetryExporter(SpanExporter, ABC):
|
|
9
|
+
"""Base class for custom OpenTelemetry exporters."""
|
|
10
|
+
|
|
11
|
+
def __init__(self):
|
|
12
|
+
"""Base class for custom OpenTelemetry exporters."""
|
|
13
|
+
super().__init__()
|
|
14
|
+
|
|
15
|
+
def _export(self, spans):
|
|
16
|
+
"""Forward spans to the Jaeger exporter."""
|
|
17
|
+
try:
|
|
18
|
+
result = self.export(spans)
|
|
19
|
+
if result is None:
|
|
20
|
+
return SpanExportResult.SUCCESS
|
|
21
|
+
return result
|
|
22
|
+
except Exception:
|
|
23
|
+
return SpanExportResult.FAILURE
|
|
24
|
+
finally:
|
|
25
|
+
self.shutdown()
|
|
26
|
+
|
|
27
|
+
@abstractmethod
|
|
28
|
+
def export(self, spans) -> SpanExportResult | None:
|
|
29
|
+
"""Export spans to the configured backend.
|
|
30
|
+
|
|
31
|
+
To be implemented by subclasses.
|
|
32
|
+
"""
|
|
33
|
+
raise NotImplementedError("Subclasses must implement the export method")
|
|
34
|
+
|
|
35
|
+
@abstractmethod
|
|
36
|
+
def shutdown(self):
|
|
37
|
+
"""Cleanup resources, if any. Optional for subclasses."""
|
|
38
|
+
pass
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"""A simple exporter that writes span data as JSON lines into a file."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
|
|
5
|
+
from opentelemetry.sdk.trace.export import SpanExportResult
|
|
6
|
+
from opentelemetry.trace import Status, StatusCode
|
|
7
|
+
from temporalio import workflow
|
|
8
|
+
|
|
9
|
+
from flock.core.logging.telemetry_exporter.base_exporter import (
|
|
10
|
+
TelemetryExporter,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
with workflow.unsafe.imports_passed_through():
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class FileSpanExporter(TelemetryExporter):
|
|
18
|
+
"""A simple exporter that writes span data as JSON lines into a file."""
|
|
19
|
+
|
|
20
|
+
def __init__(self, dir: str, file_path: str = "flock_events.jsonl"):
|
|
21
|
+
"""Initialize the exporter with a file path."""
|
|
22
|
+
super().__init__()
|
|
23
|
+
self.telemetry_path = Path(dir)
|
|
24
|
+
self.telemetry_path.mkdir(parents=True, exist_ok=True)
|
|
25
|
+
self.file_path = self.telemetry_path.joinpath(file_path).__str__()
|
|
26
|
+
|
|
27
|
+
def _span_to_json(self, span):
|
|
28
|
+
"""Convert a ReadableSpan to a JSON-serializable dict."""
|
|
29
|
+
context = span.get_span_context()
|
|
30
|
+
status = span.status or Status(StatusCode.UNSET)
|
|
31
|
+
|
|
32
|
+
return {
|
|
33
|
+
"name": span.name,
|
|
34
|
+
"context": {
|
|
35
|
+
"trace_id": format(context.trace_id, "032x"),
|
|
36
|
+
"span_id": format(context.span_id, "016x"),
|
|
37
|
+
"trace_flags": context.trace_flags,
|
|
38
|
+
"trace_state": str(context.trace_state),
|
|
39
|
+
},
|
|
40
|
+
"kind": span.kind.name if span.kind else None,
|
|
41
|
+
"start_time": span.start_time,
|
|
42
|
+
"end_time": span.end_time,
|
|
43
|
+
"status": {
|
|
44
|
+
"status_code": status.status_code.name,
|
|
45
|
+
"description": status.description,
|
|
46
|
+
},
|
|
47
|
+
"attributes": dict(span.attributes or {}),
|
|
48
|
+
"events": [
|
|
49
|
+
{
|
|
50
|
+
"name": event.name,
|
|
51
|
+
"timestamp": event.timestamp,
|
|
52
|
+
"attributes": dict(event.attributes or {}),
|
|
53
|
+
}
|
|
54
|
+
for event in span.events
|
|
55
|
+
],
|
|
56
|
+
"links": [
|
|
57
|
+
{
|
|
58
|
+
"context": {
|
|
59
|
+
"trace_id": format(link.context.trace_id, "032x"),
|
|
60
|
+
"span_id": format(link.context.span_id, "016x"),
|
|
61
|
+
},
|
|
62
|
+
"attributes": dict(link.attributes or {}),
|
|
63
|
+
}
|
|
64
|
+
for link in span.links
|
|
65
|
+
],
|
|
66
|
+
"resource": {
|
|
67
|
+
attr_key: attr_value
|
|
68
|
+
for attr_key, attr_value in span.resource.attributes.items()
|
|
69
|
+
},
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
def export(self, spans):
|
|
73
|
+
"""Write spans to a log file."""
|
|
74
|
+
try:
|
|
75
|
+
with open(self.file_path, "a") as f:
|
|
76
|
+
for span in spans:
|
|
77
|
+
json_span = self._span_to_json(span)
|
|
78
|
+
f.write(f"{json.dumps(json_span)}\n")
|
|
79
|
+
return SpanExportResult.SUCCESS
|
|
80
|
+
except Exception:
|
|
81
|
+
return SpanExportResult.FAILURE
|
|
82
|
+
|
|
83
|
+
def shutdown(self) -> None:
|
|
84
|
+
# Nothing special needed on shutdown.
|
|
85
|
+
pass
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"""Exporter for storing OpenTelemetry spans in SQLite."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import sqlite3
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
from opentelemetry.sdk.trace.export import SpanExportResult
|
|
9
|
+
|
|
10
|
+
from flock.core.logging.telemetry_exporter.base_exporter import (
|
|
11
|
+
TelemetryExporter,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class SqliteTelemetryExporter(TelemetryExporter):
|
|
16
|
+
"""Exporter for storing OpenTelemetry spans in SQLite."""
|
|
17
|
+
|
|
18
|
+
def __init__(self, dir: str, db_path: str = "flock_events.db"):
|
|
19
|
+
"""Initialize the SQLite exporter.
|
|
20
|
+
|
|
21
|
+
Args:
|
|
22
|
+
db_path: Path to the SQLite database file
|
|
23
|
+
"""
|
|
24
|
+
super().__init__()
|
|
25
|
+
self.telemetry_path = Path(dir)
|
|
26
|
+
self.telemetry_path.mkdir(parents=True, exist_ok=True)
|
|
27
|
+
# Create an absolute path to the database file:
|
|
28
|
+
self.db_path = self.telemetry_path.joinpath(db_path).resolve().__str__()
|
|
29
|
+
# Use the absolute path when connecting:
|
|
30
|
+
self.conn = sqlite3.connect(self.db_path, check_same_thread=False)
|
|
31
|
+
self._initialize_database()
|
|
32
|
+
|
|
33
|
+
def _initialize_database(self):
|
|
34
|
+
"""Set up the SQLite database schema."""
|
|
35
|
+
cursor = self.conn.cursor()
|
|
36
|
+
cursor.execute(
|
|
37
|
+
"""
|
|
38
|
+
CREATE TABLE IF NOT EXISTS spans (
|
|
39
|
+
id TEXT PRIMARY KEY,
|
|
40
|
+
name TEXT,
|
|
41
|
+
trace_id TEXT,
|
|
42
|
+
span_id TEXT,
|
|
43
|
+
start_time INTEGER,
|
|
44
|
+
end_time INTEGER,
|
|
45
|
+
attributes TEXT,
|
|
46
|
+
status TEXT
|
|
47
|
+
)
|
|
48
|
+
"""
|
|
49
|
+
)
|
|
50
|
+
self.conn.commit()
|
|
51
|
+
|
|
52
|
+
def _convert_attributes(self, attributes: dict[str, Any]) -> str:
|
|
53
|
+
"""Convert span attributes to a JSON string.
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
attributes: Dictionary of span attributes
|
|
57
|
+
|
|
58
|
+
Returns:
|
|
59
|
+
JSON string representation of attributes
|
|
60
|
+
"""
|
|
61
|
+
# Convert attributes to a serializable format
|
|
62
|
+
serializable_attrs = {}
|
|
63
|
+
for key, value in attributes.items():
|
|
64
|
+
# Convert complex types to strings if needed
|
|
65
|
+
if isinstance(value, dict | list | tuple):
|
|
66
|
+
serializable_attrs[key] = json.dumps(value)
|
|
67
|
+
else:
|
|
68
|
+
serializable_attrs[key] = str(value)
|
|
69
|
+
return json.dumps(serializable_attrs)
|
|
70
|
+
|
|
71
|
+
def export(self, spans) -> SpanExportResult:
|
|
72
|
+
"""Export spans to SQLite."""
|
|
73
|
+
try:
|
|
74
|
+
cursor = self.conn.cursor()
|
|
75
|
+
for span in spans:
|
|
76
|
+
span_id = format(span.context.span_id, "016x")
|
|
77
|
+
trace_id = format(span.context.trace_id, "032x")
|
|
78
|
+
cursor.execute(
|
|
79
|
+
"""
|
|
80
|
+
INSERT OR REPLACE INTO spans
|
|
81
|
+
(id, name, trace_id, span_id, start_time, end_time, attributes, status)
|
|
82
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
83
|
+
""",
|
|
84
|
+
(
|
|
85
|
+
span_id,
|
|
86
|
+
span.name,
|
|
87
|
+
trace_id,
|
|
88
|
+
span_id,
|
|
89
|
+
span.start_time,
|
|
90
|
+
span.end_time,
|
|
91
|
+
self._convert_attributes(span.attributes),
|
|
92
|
+
str(span.status),
|
|
93
|
+
),
|
|
94
|
+
)
|
|
95
|
+
self.conn.commit()
|
|
96
|
+
return SpanExportResult.SUCCESS
|
|
97
|
+
except Exception as e:
|
|
98
|
+
print("Error exporting spans to SQLite:", e)
|
|
99
|
+
return SpanExportResult.FAILURE
|
|
100
|
+
|
|
101
|
+
def shutdown(self) -> None:
|
|
102
|
+
"""Cleanup resources."""
|
|
103
|
+
pass
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
"""A decorator that wraps a function in an OpenTelemetry span and logs its inputs, outputs, and exceptions."""
|
|
2
|
+
|
|
1
3
|
import functools
|
|
2
4
|
import inspect
|
|
3
5
|
|
|
@@ -10,7 +12,9 @@ tracer = trace.get_tracer(__name__)
|
|
|
10
12
|
|
|
11
13
|
|
|
12
14
|
def traced_and_logged(func):
|
|
13
|
-
"""A decorator that wraps a function in an OpenTelemetry span
|
|
15
|
+
"""A decorator that wraps a function in an OpenTelemetry span.
|
|
16
|
+
|
|
17
|
+
and logs its inputs,
|
|
14
18
|
outputs, and exceptions. Supports both synchronous and asynchronous functions.
|
|
15
19
|
"""
|
|
16
20
|
if inspect.iscoroutinefunction(func):
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import subprocess
|
|
2
|
+
import time
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def _check_docker_running():
|
|
6
|
+
"""Check if Docker is running by calling 'docker info'."""
|
|
7
|
+
try:
|
|
8
|
+
result = subprocess.run(
|
|
9
|
+
["docker", "info"],
|
|
10
|
+
stdout=subprocess.PIPE,
|
|
11
|
+
stderr=subprocess.PIPE,
|
|
12
|
+
text=True,
|
|
13
|
+
)
|
|
14
|
+
return result.returncode == 0
|
|
15
|
+
except Exception:
|
|
16
|
+
return False
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _start_docker():
|
|
20
|
+
"""Attempt to start Docker.
|
|
21
|
+
This example first tries 'systemctl start docker' and then 'service docker start'.
|
|
22
|
+
Adjust as needed for your environment.
|
|
23
|
+
"""
|
|
24
|
+
try:
|
|
25
|
+
print("Attempting to start Docker...")
|
|
26
|
+
result = subprocess.run(
|
|
27
|
+
["sudo", "systemctl", "start", "docker"],
|
|
28
|
+
stdout=subprocess.PIPE,
|
|
29
|
+
stderr=subprocess.PIPE,
|
|
30
|
+
text=True,
|
|
31
|
+
)
|
|
32
|
+
if result.returncode != 0:
|
|
33
|
+
result = subprocess.run(
|
|
34
|
+
["sudo", "service", "docker", "start"],
|
|
35
|
+
stdout=subprocess.PIPE,
|
|
36
|
+
stderr=subprocess.PIPE,
|
|
37
|
+
text=True,
|
|
38
|
+
)
|
|
39
|
+
# Give Docker a moment to start.
|
|
40
|
+
time.sleep(3)
|
|
41
|
+
if _check_docker_running():
|
|
42
|
+
print("Docker is now running.")
|
|
43
|
+
return True
|
|
44
|
+
else:
|
|
45
|
+
print("Docker did not start successfully.")
|
|
46
|
+
return False
|
|
47
|
+
except Exception as e:
|
|
48
|
+
print(f"Exception when trying to start Docker: {e}")
|
|
49
|
+
return False
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import socket
|
|
2
|
+
import subprocess
|
|
3
|
+
from urllib.parse import urlparse
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class JaegerInstaller:
|
|
7
|
+
jaeger_endpoint: str = None
|
|
8
|
+
jaeger_transport: str = "grpc"
|
|
9
|
+
|
|
10
|
+
def _check_jaeger_running(self):
|
|
11
|
+
"""Check if Jaeger is reachable by attempting a socket connection.
|
|
12
|
+
For HTTP transport, we parse the URL; for gRPC, we expect "host:port".
|
|
13
|
+
"""
|
|
14
|
+
try:
|
|
15
|
+
if self.jaeger_transport == "grpc":
|
|
16
|
+
host, port = self.jaeger_endpoint.split(":")
|
|
17
|
+
port = int(port)
|
|
18
|
+
elif self.jaeger_transport == "http":
|
|
19
|
+
parsed = urlparse(self.jaeger_endpoint)
|
|
20
|
+
host = parsed.hostname
|
|
21
|
+
port = parsed.port if parsed.port else 80
|
|
22
|
+
else:
|
|
23
|
+
return False
|
|
24
|
+
|
|
25
|
+
# Try connecting to the host and port.
|
|
26
|
+
with socket.create_connection((host, port), timeout=3):
|
|
27
|
+
return True
|
|
28
|
+
except Exception:
|
|
29
|
+
return False
|
|
30
|
+
|
|
31
|
+
def _is_jaeger_container_running(self):
|
|
32
|
+
"""Check if a Jaeger container (using the official all-in-one image) is running.
|
|
33
|
+
This uses 'docker ps' to filter for containers running the Jaeger image.
|
|
34
|
+
"""
|
|
35
|
+
try:
|
|
36
|
+
result = subprocess.run(
|
|
37
|
+
[
|
|
38
|
+
"docker",
|
|
39
|
+
"ps",
|
|
40
|
+
"--filter",
|
|
41
|
+
"ancestor=jaegertracing/all-in-one:latest",
|
|
42
|
+
"--format",
|
|
43
|
+
"{{.ID}}",
|
|
44
|
+
],
|
|
45
|
+
stdout=subprocess.PIPE,
|
|
46
|
+
stderr=subprocess.PIPE,
|
|
47
|
+
text=True,
|
|
48
|
+
)
|
|
49
|
+
return bool(result.stdout.strip())
|
|
50
|
+
except Exception:
|
|
51
|
+
return False
|
|
52
|
+
|
|
53
|
+
def _provision_jaeger_container(self):
|
|
54
|
+
"""Provision a Jaeger container using Docker."""
|
|
55
|
+
try:
|
|
56
|
+
print("Provisioning Jaeger container using Docker...")
|
|
57
|
+
result = subprocess.run(
|
|
58
|
+
[
|
|
59
|
+
"docker",
|
|
60
|
+
"run",
|
|
61
|
+
"-d",
|
|
62
|
+
"--name",
|
|
63
|
+
"jaeger",
|
|
64
|
+
"-p",
|
|
65
|
+
"16686:16686",
|
|
66
|
+
"-p",
|
|
67
|
+
"14250:14250",
|
|
68
|
+
"-p",
|
|
69
|
+
"14268:14268",
|
|
70
|
+
"jaegertracing/all-in-one:latest",
|
|
71
|
+
],
|
|
72
|
+
stdout=subprocess.PIPE,
|
|
73
|
+
stderr=subprocess.PIPE,
|
|
74
|
+
text=True,
|
|
75
|
+
)
|
|
76
|
+
if result.returncode == 0:
|
|
77
|
+
print("Jaeger container started successfully.")
|
|
78
|
+
return True
|
|
79
|
+
else:
|
|
80
|
+
print(
|
|
81
|
+
f"Failed to start Jaeger container. Error: {result.stderr}"
|
|
82
|
+
)
|
|
83
|
+
return False
|
|
84
|
+
except Exception as e:
|
|
85
|
+
print(f"Exception when provisioning Jaeger container: {e}")
|
|
86
|
+
return False
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: flock-core
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.5
|
|
4
4
|
Summary: Declarative LLM Orchestration at Scale
|
|
5
5
|
Author-email: Andre Ratzenberger <andre.ratzenberger@whiteduck.de>
|
|
6
6
|
License-File: LICENSE
|
|
@@ -11,6 +11,7 @@ Requires-Python: >=3.10
|
|
|
11
11
|
Requires-Dist: cloudpickle>=3.1.1
|
|
12
12
|
Requires-Dist: devtools>=0.12.2
|
|
13
13
|
Requires-Dist: dspy==2.5.42
|
|
14
|
+
Requires-Dist: duckduckgo-search>=7.3.2
|
|
14
15
|
Requires-Dist: httpx>=0.28.1
|
|
15
16
|
Requires-Dist: loguru>=0.7.3
|
|
16
17
|
Requires-Dist: msgpack>=1.1.0
|
|
@@ -22,6 +23,7 @@ Requires-Dist: opentelemetry-instrumentation-logging>=0.51b0
|
|
|
22
23
|
Requires-Dist: opentelemetry-sdk>=1.30.0
|
|
23
24
|
Requires-Dist: pydantic>=2.10.5
|
|
24
25
|
Requires-Dist: python-box>=7.3.2
|
|
26
|
+
Requires-Dist: python-decouple>=3.8
|
|
25
27
|
Requires-Dist: rich>=13.9.4
|
|
26
28
|
Requires-Dist: temporalio>=1.9.0
|
|
27
29
|
Requires-Dist: toml>=0.10.2
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
flock/__init__.py,sha256=
|
|
2
|
-
flock/config.py,sha256=
|
|
1
|
+
flock/__init__.py,sha256=P175tsTOByIhw_CIeMAybUEggKhtoSrc8a0gKotBJfo,177
|
|
2
|
+
flock/config.py,sha256=dyLqNng9ETTEx3CRCkz5N4g62Nu4ygPA6uQKEdbSgag,1499
|
|
3
3
|
flock/core/__init__.py,sha256=0Xq_txurlxxjKGXjRn6GNJusGTiBcd7zw2eF0L7JyuU,183
|
|
4
|
-
flock/core/flock.py,sha256=
|
|
4
|
+
flock/core/flock.py,sha256=0NC-J_ZCojwWDepI6rbX-9jG_Hr2AKgY43ieijhD8DU,9820
|
|
5
5
|
flock/core/flock_agent.py,sha256=59qQ7ohOy2lc1KjI6SV7IcrqYL86ofAhq32pZGgk6eA,27761
|
|
6
6
|
flock/core/context/context.py,sha256=jH06w4C_O5CEL-YxjX_x_dmgLe9Rcllnn1Ebs0dvwaE,6171
|
|
7
7
|
flock/core/context/context_manager.py,sha256=qMySVny_dbTNLh21RHK_YT0mNKIOrqJDZpi9ZVdBsxU,1103
|
|
@@ -9,17 +9,19 @@ flock/core/context/context_vars.py,sha256=0Hn6fM2iNc0_jIIU0B7KX-K2o8qXqtZ5EYtwuj
|
|
|
9
9
|
flock/core/execution/local_executor.py,sha256=O_dgQ_HJPCp97ghdEoDSNDIiaYkogrUS0G2FfK04RRc,973
|
|
10
10
|
flock/core/execution/temporal_executor.py,sha256=ai6ikr9rEiN2Kc-208OalxtfqL_FTt_UaH6a--oEkJM,2010
|
|
11
11
|
flock/core/logging/__init__.py,sha256=Q8hp9-1ilPIUIV0jLgJ3_cP7COrea32cVwL7dicPnlM,82
|
|
12
|
-
flock/core/logging/logging.py,sha256=
|
|
13
|
-
flock/core/logging/telemetry.py,sha256=
|
|
14
|
-
flock/core/logging/trace_and_logged.py,sha256=
|
|
12
|
+
flock/core/logging/logging.py,sha256=dOo_McbAt9_dST9Hr8RTpAGU-MHR_QvskIdmXrSvZRc,4789
|
|
13
|
+
flock/core/logging/telemetry.py,sha256=yEOfEZ3HBFeLCaHZA6QmsRdwZKtmUC6bQtEOTVeRR4o,5314
|
|
14
|
+
flock/core/logging/trace_and_logged.py,sha256=5vNrK1kxuPMoPJ0-QjQg-EDJL1oiEzvU6UNi6X8FiMs,2117
|
|
15
15
|
flock/core/logging/formatters/base_formatter.py,sha256=CyG-X2NWq8sqEhFEO2aG7Mey5tVkIzoWiihW301_VIo,1023
|
|
16
16
|
flock/core/logging/formatters/formatter_factory.py,sha256=hmH-NpCESHkioX0GBQ5CuQR4axyIXnSRWwAZCHylx6Q,1283
|
|
17
17
|
flock/core/logging/formatters/pprint_formatter.py,sha256=tTm2WhwlCw-SX2Ouci5I9U_HVgxNGY5SSnzB9HZh8bg,692
|
|
18
18
|
flock/core/logging/formatters/rich_formatters.py,sha256=h1FD0_cIdQBQ8P2x05XhgD1cmmP80IBNVT5jb3cAV9M,4776
|
|
19
19
|
flock/core/logging/formatters/theme_builder.py,sha256=1RUEwPIDfCjwTapbK1liasA5SdukOn7YwbZ4H4j1WkI,17364
|
|
20
20
|
flock/core/logging/formatters/themed_formatter.py,sha256=CbxmqUC7zkLzyIxngk-3dcpQ6vxPR6zaDNA2TAMitCI,16714
|
|
21
|
-
flock/core/logging/
|
|
22
|
-
flock/core/logging/telemetry_exporter/
|
|
21
|
+
flock/core/logging/span_middleware/baggage_span_processor.py,sha256=gJfRl8FeB6jdtghTaRHCrOaTo4fhPMRKgjqtZj-8T48,1118
|
|
22
|
+
flock/core/logging/telemetry_exporter/base_exporter.py,sha256=rQJJzS6q9n2aojoSqwCnl7ZtHrh5LZZ-gkxUuI5WfrQ,1124
|
|
23
|
+
flock/core/logging/telemetry_exporter/file_exporter.py,sha256=nKAjJSZtA7FqHSTuTiFtYYepaxOq7l1rDvs8U8rSBlA,3023
|
|
24
|
+
flock/core/logging/telemetry_exporter/sqlite_exporter.py,sha256=CDsiMb9QcqeXelZ6ZqPSS56ovMPGqOu6whzBZRK__Vg,3498
|
|
23
25
|
flock/core/mixin/dspy_integration.py,sha256=oT5YfXxPhHkMCuwhXoppBAYBGePUAKse7KebGSM-bq0,6880
|
|
24
26
|
flock/core/mixin/prompt_parser.py,sha256=eOqI-FK3y17gVqpc_y5GF-WmK1Jv8mFlkZxTcgweoxI,5121
|
|
25
27
|
flock/core/registry/agent_registry.py,sha256=QHdr3Cb-32PEdz8jFCIZSH9OlfpRwAJMtSRpHCWJDq4,4889
|
|
@@ -28,6 +30,8 @@ flock/core/tools/dev_tools/github.py,sha256=6ya2_eN-qITV3b_pYP24jQC3X4oZbRY5GKh1
|
|
|
28
30
|
flock/core/util/cli_helper.py,sha256=aHLKjl5JBLIczLzjYeUcGQlVQRlypunxV2TYeAFX0KE,1030
|
|
29
31
|
flock/core/util/input_resolver.py,sha256=OesGqX2Dld8myL9Qz04mmxLqoYqOSQC632pj1EMk9Yk,5456
|
|
30
32
|
flock/core/util/serializable.py,sha256=SymJ0YrjBx48mOBItYSqoRpKuzIc4vKWRS6ScTzre7s,2573
|
|
33
|
+
flock/platform/docker_tools.py,sha256=fpA7-6rJBjPOUBLdQP4ny2QPgJ_042nmqRn5GtKnoYw,1445
|
|
34
|
+
flock/platform/jaeger_install.py,sha256=MyOMJQx4TQSMYvdUJxfiGSo3YCtsfkbNXcAcQ9bjETA,2898
|
|
31
35
|
flock/themes/3024-day.toml,sha256=uOVHqEzSyHx0WlUk3D0lne4RBsNBAPCTy3C58yU7kEY,667
|
|
32
36
|
flock/themes/3024-night.toml,sha256=qsXUwd6ZYz6J-R129_Ao2TKlvvK60svhZJJjB5c8Tfo,1667
|
|
33
37
|
flock/themes/aardvark-blue.toml,sha256=Px1qevE6J1ZAc_jAqF_FX674KdIv_3pAYNKmmvDxIbE,1672
|
|
@@ -369,7 +373,8 @@ flock/workflow/activities.py,sha256=YEg-Gr8kzVsxWsmsZguIVhX2XwMRvhZ2OlnsJoG5g_A,
|
|
|
369
373
|
flock/workflow/agent_activities.py,sha256=NhBZscflEf2IMfSRa_pBM_TRP7uVEF_O0ROvWZ33eDc,963
|
|
370
374
|
flock/workflow/temporal_setup.py,sha256=VWBgmBgfTBjwM5ruS_dVpA5AVxx6EZ7oFPGw4j3m0l0,1091
|
|
371
375
|
flock/workflow/workflow.py,sha256=I9MryXW_bqYVTHx-nl2epbTqeRy27CAWHHA7ZZA0nAk,1696
|
|
372
|
-
flock_core-0.2.
|
|
373
|
-
flock_core-0.2.
|
|
374
|
-
flock_core-0.2.
|
|
375
|
-
flock_core-0.2.
|
|
376
|
+
flock_core-0.2.5.dist-info/METADATA,sha256=T9Yd-0aEUNgHlQnQp64zeDIkDgAsQRoPMF3WnBLUgCk,11564
|
|
377
|
+
flock_core-0.2.5.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
378
|
+
flock_core-0.2.5.dist-info/entry_points.txt,sha256=rWaS5KSpkTmWySURGFZk6PhbJ87TmvcFQDi2uzjlagQ,37
|
|
379
|
+
flock_core-0.2.5.dist-info/licenses/LICENSE,sha256=iYEqWy0wjULzM9GAERaybP4LBiPeu7Z1NEliLUdJKSc,1072
|
|
380
|
+
flock_core-0.2.5.dist-info/RECORD,,
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
import json
|
|
2
|
-
|
|
3
|
-
from opentelemetry.sdk.trace.export import (
|
|
4
|
-
SpanExporter,
|
|
5
|
-
SpanExportResult,
|
|
6
|
-
)
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
class FileSpanExporter(SpanExporter):
|
|
10
|
-
"""A simple exporter that writes span data as JSON lines into a file."""
|
|
11
|
-
|
|
12
|
-
def __init__(self, file_path: str):
|
|
13
|
-
self.file_path = file_path
|
|
14
|
-
|
|
15
|
-
def export(self, spans) -> SpanExportResult:
|
|
16
|
-
try:
|
|
17
|
-
with open(self.file_path, "a") as f:
|
|
18
|
-
for span in spans:
|
|
19
|
-
# Create a dictionary representation of the span.
|
|
20
|
-
span_dict = {
|
|
21
|
-
"name": span.name,
|
|
22
|
-
"trace_id": format(span.context.trace_id, "032x"),
|
|
23
|
-
"span_id": format(span.context.span_id, "016x"),
|
|
24
|
-
"start_time": span.start_time,
|
|
25
|
-
"end_time": span.end_time,
|
|
26
|
-
"attributes": span.attributes,
|
|
27
|
-
"status": str(span.status),
|
|
28
|
-
}
|
|
29
|
-
f.write(json.dumps(span_dict) + "\n")
|
|
30
|
-
return SpanExportResult.SUCCESS
|
|
31
|
-
except Exception as e:
|
|
32
|
-
print("Error exporting spans to file:", e)
|
|
33
|
-
return SpanExportResult.FAILURE
|
|
34
|
-
|
|
35
|
-
def shutdown(self) -> None:
|
|
36
|
-
# Nothing special needed on shutdown.
|
|
37
|
-
pass
|
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
import json
|
|
2
|
-
import sqlite3
|
|
3
|
-
|
|
4
|
-
from opentelemetry.sdk.trace.export import (
|
|
5
|
-
SpanExporter,
|
|
6
|
-
SpanExportResult,
|
|
7
|
-
)
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
class SQLiteSpanExporter(SpanExporter):
|
|
11
|
-
"""A custom exporter that writes span data into a SQLite database."""
|
|
12
|
-
|
|
13
|
-
def __init__(self, sqlite_db_path: str):
|
|
14
|
-
self.sqlite_db_path = sqlite_db_path
|
|
15
|
-
self.conn = sqlite3.connect(
|
|
16
|
-
self.sqlite_db_path, check_same_thread=False
|
|
17
|
-
)
|
|
18
|
-
self._create_table()
|
|
19
|
-
|
|
20
|
-
def _create_table(self):
|
|
21
|
-
cursor = self.conn.cursor()
|
|
22
|
-
cursor.execute(
|
|
23
|
-
"""
|
|
24
|
-
CREATE TABLE IF NOT EXISTS spans (
|
|
25
|
-
id TEXT PRIMARY KEY,
|
|
26
|
-
name TEXT,
|
|
27
|
-
trace_id TEXT,
|
|
28
|
-
span_id TEXT,
|
|
29
|
-
start_time INTEGER,
|
|
30
|
-
end_time INTEGER,
|
|
31
|
-
attributes TEXT,
|
|
32
|
-
status TEXT
|
|
33
|
-
)
|
|
34
|
-
"""
|
|
35
|
-
)
|
|
36
|
-
self.conn.commit()
|
|
37
|
-
|
|
38
|
-
def export(self, spans) -> SpanExportResult:
|
|
39
|
-
try:
|
|
40
|
-
cursor = self.conn.cursor()
|
|
41
|
-
for span in spans:
|
|
42
|
-
span_id = format(span.context.span_id, "016x")
|
|
43
|
-
trace_id = format(span.context.trace_id, "032x")
|
|
44
|
-
cursor.execute(
|
|
45
|
-
"""
|
|
46
|
-
INSERT OR REPLACE INTO spans
|
|
47
|
-
(id, name, trace_id, span_id, start_time, end_time, attributes, status)
|
|
48
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
49
|
-
""",
|
|
50
|
-
(
|
|
51
|
-
span_id,
|
|
52
|
-
span.name,
|
|
53
|
-
trace_id,
|
|
54
|
-
span_id,
|
|
55
|
-
span.start_time,
|
|
56
|
-
span.end_time,
|
|
57
|
-
json.dumps(span.attributes),
|
|
58
|
-
str(span.status),
|
|
59
|
-
),
|
|
60
|
-
)
|
|
61
|
-
self.conn.commit()
|
|
62
|
-
return SpanExportResult.SUCCESS
|
|
63
|
-
except Exception as e:
|
|
64
|
-
print("Error exporting spans to SQLite:", e)
|
|
65
|
-
return SpanExportResult.FAILURE
|
|
66
|
-
|
|
67
|
-
def shutdown(self) -> None:
|
|
68
|
-
self.conn.close()
|
|
File without changes
|
|
File without changes
|