oscura 0.0.1__py3-none-any.whl → 0.1.1__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.
- oscura/__init__.py +813 -8
- oscura/__main__.py +392 -0
- oscura/analyzers/__init__.py +37 -0
- oscura/analyzers/digital/__init__.py +177 -0
- oscura/analyzers/digital/bus.py +691 -0
- oscura/analyzers/digital/clock.py +805 -0
- oscura/analyzers/digital/correlation.py +720 -0
- oscura/analyzers/digital/edges.py +632 -0
- oscura/analyzers/digital/extraction.py +413 -0
- oscura/analyzers/digital/quality.py +878 -0
- oscura/analyzers/digital/signal_quality.py +877 -0
- oscura/analyzers/digital/thresholds.py +708 -0
- oscura/analyzers/digital/timing.py +1104 -0
- oscura/analyzers/eye/__init__.py +46 -0
- oscura/analyzers/eye/diagram.py +434 -0
- oscura/analyzers/eye/metrics.py +555 -0
- oscura/analyzers/jitter/__init__.py +83 -0
- oscura/analyzers/jitter/ber.py +333 -0
- oscura/analyzers/jitter/decomposition.py +759 -0
- oscura/analyzers/jitter/measurements.py +413 -0
- oscura/analyzers/jitter/spectrum.py +220 -0
- oscura/analyzers/measurements.py +40 -0
- oscura/analyzers/packet/__init__.py +171 -0
- oscura/analyzers/packet/daq.py +1077 -0
- oscura/analyzers/packet/metrics.py +437 -0
- oscura/analyzers/packet/parser.py +327 -0
- oscura/analyzers/packet/payload.py +2156 -0
- oscura/analyzers/packet/payload_analysis.py +1312 -0
- oscura/analyzers/packet/payload_extraction.py +236 -0
- oscura/analyzers/packet/payload_patterns.py +670 -0
- oscura/analyzers/packet/stream.py +359 -0
- oscura/analyzers/patterns/__init__.py +266 -0
- oscura/analyzers/patterns/clustering.py +1036 -0
- oscura/analyzers/patterns/discovery.py +539 -0
- oscura/analyzers/patterns/learning.py +797 -0
- oscura/analyzers/patterns/matching.py +1091 -0
- oscura/analyzers/patterns/periodic.py +650 -0
- oscura/analyzers/patterns/sequences.py +767 -0
- oscura/analyzers/power/__init__.py +116 -0
- oscura/analyzers/power/ac_power.py +391 -0
- oscura/analyzers/power/basic.py +383 -0
- oscura/analyzers/power/conduction.py +314 -0
- oscura/analyzers/power/efficiency.py +297 -0
- oscura/analyzers/power/ripple.py +356 -0
- oscura/analyzers/power/soa.py +372 -0
- oscura/analyzers/power/switching.py +479 -0
- oscura/analyzers/protocol/__init__.py +150 -0
- oscura/analyzers/protocols/__init__.py +150 -0
- oscura/analyzers/protocols/base.py +500 -0
- oscura/analyzers/protocols/can.py +620 -0
- oscura/analyzers/protocols/can_fd.py +448 -0
- oscura/analyzers/protocols/flexray.py +405 -0
- oscura/analyzers/protocols/hdlc.py +399 -0
- oscura/analyzers/protocols/i2c.py +368 -0
- oscura/analyzers/protocols/i2s.py +296 -0
- oscura/analyzers/protocols/jtag.py +393 -0
- oscura/analyzers/protocols/lin.py +445 -0
- oscura/analyzers/protocols/manchester.py +333 -0
- oscura/analyzers/protocols/onewire.py +501 -0
- oscura/analyzers/protocols/spi.py +334 -0
- oscura/analyzers/protocols/swd.py +325 -0
- oscura/analyzers/protocols/uart.py +393 -0
- oscura/analyzers/protocols/usb.py +495 -0
- oscura/analyzers/signal_integrity/__init__.py +63 -0
- oscura/analyzers/signal_integrity/embedding.py +294 -0
- oscura/analyzers/signal_integrity/equalization.py +370 -0
- oscura/analyzers/signal_integrity/sparams.py +484 -0
- oscura/analyzers/spectral/__init__.py +53 -0
- oscura/analyzers/spectral/chunked.py +273 -0
- oscura/analyzers/spectral/chunked_fft.py +571 -0
- oscura/analyzers/spectral/chunked_wavelet.py +391 -0
- oscura/analyzers/spectral/fft.py +92 -0
- oscura/analyzers/statistical/__init__.py +250 -0
- oscura/analyzers/statistical/checksum.py +923 -0
- oscura/analyzers/statistical/chunked_corr.py +228 -0
- oscura/analyzers/statistical/classification.py +778 -0
- oscura/analyzers/statistical/entropy.py +1113 -0
- oscura/analyzers/statistical/ngrams.py +614 -0
- oscura/analyzers/statistics/__init__.py +119 -0
- oscura/analyzers/statistics/advanced.py +885 -0
- oscura/analyzers/statistics/basic.py +263 -0
- oscura/analyzers/statistics/correlation.py +630 -0
- oscura/analyzers/statistics/distribution.py +298 -0
- oscura/analyzers/statistics/outliers.py +463 -0
- oscura/analyzers/statistics/streaming.py +93 -0
- oscura/analyzers/statistics/trend.py +520 -0
- oscura/analyzers/validation.py +598 -0
- oscura/analyzers/waveform/__init__.py +36 -0
- oscura/analyzers/waveform/measurements.py +943 -0
- oscura/analyzers/waveform/measurements_with_uncertainty.py +371 -0
- oscura/analyzers/waveform/spectral.py +1689 -0
- oscura/analyzers/waveform/wavelets.py +298 -0
- oscura/api/__init__.py +62 -0
- oscura/api/dsl.py +538 -0
- oscura/api/fluent.py +571 -0
- oscura/api/operators.py +498 -0
- oscura/api/optimization.py +392 -0
- oscura/api/profiling.py +396 -0
- oscura/automotive/__init__.py +73 -0
- oscura/automotive/can/__init__.py +52 -0
- oscura/automotive/can/analysis.py +356 -0
- oscura/automotive/can/checksum.py +250 -0
- oscura/automotive/can/correlation.py +212 -0
- oscura/automotive/can/discovery.py +355 -0
- oscura/automotive/can/message_wrapper.py +375 -0
- oscura/automotive/can/models.py +385 -0
- oscura/automotive/can/patterns.py +381 -0
- oscura/automotive/can/session.py +452 -0
- oscura/automotive/can/state_machine.py +300 -0
- oscura/automotive/can/stimulus_response.py +461 -0
- oscura/automotive/dbc/__init__.py +15 -0
- oscura/automotive/dbc/generator.py +156 -0
- oscura/automotive/dbc/parser.py +146 -0
- oscura/automotive/dtc/__init__.py +30 -0
- oscura/automotive/dtc/database.py +3036 -0
- oscura/automotive/j1939/__init__.py +14 -0
- oscura/automotive/j1939/decoder.py +745 -0
- oscura/automotive/loaders/__init__.py +35 -0
- oscura/automotive/loaders/asc.py +98 -0
- oscura/automotive/loaders/blf.py +77 -0
- oscura/automotive/loaders/csv_can.py +136 -0
- oscura/automotive/loaders/dispatcher.py +136 -0
- oscura/automotive/loaders/mdf.py +331 -0
- oscura/automotive/loaders/pcap.py +132 -0
- oscura/automotive/obd/__init__.py +14 -0
- oscura/automotive/obd/decoder.py +707 -0
- oscura/automotive/uds/__init__.py +48 -0
- oscura/automotive/uds/decoder.py +265 -0
- oscura/automotive/uds/models.py +64 -0
- oscura/automotive/visualization.py +369 -0
- oscura/batch/__init__.py +55 -0
- oscura/batch/advanced.py +627 -0
- oscura/batch/aggregate.py +300 -0
- oscura/batch/analyze.py +139 -0
- oscura/batch/logging.py +487 -0
- oscura/batch/metrics.py +556 -0
- oscura/builders/__init__.py +41 -0
- oscura/builders/signal_builder.py +1131 -0
- oscura/cli/__init__.py +14 -0
- oscura/cli/batch.py +339 -0
- oscura/cli/characterize.py +273 -0
- oscura/cli/compare.py +775 -0
- oscura/cli/decode.py +551 -0
- oscura/cli/main.py +247 -0
- oscura/cli/shell.py +350 -0
- oscura/comparison/__init__.py +66 -0
- oscura/comparison/compare.py +397 -0
- oscura/comparison/golden.py +487 -0
- oscura/comparison/limits.py +391 -0
- oscura/comparison/mask.py +434 -0
- oscura/comparison/trace_diff.py +30 -0
- oscura/comparison/visualization.py +481 -0
- oscura/compliance/__init__.py +70 -0
- oscura/compliance/advanced.py +756 -0
- oscura/compliance/masks.py +363 -0
- oscura/compliance/reporting.py +483 -0
- oscura/compliance/testing.py +298 -0
- oscura/component/__init__.py +38 -0
- oscura/component/impedance.py +365 -0
- oscura/component/reactive.py +598 -0
- oscura/component/transmission_line.py +312 -0
- oscura/config/__init__.py +191 -0
- oscura/config/defaults.py +254 -0
- oscura/config/loader.py +348 -0
- oscura/config/memory.py +271 -0
- oscura/config/migration.py +458 -0
- oscura/config/pipeline.py +1077 -0
- oscura/config/preferences.py +530 -0
- oscura/config/protocol.py +875 -0
- oscura/config/schema.py +713 -0
- oscura/config/settings.py +420 -0
- oscura/config/thresholds.py +599 -0
- oscura/convenience.py +457 -0
- oscura/core/__init__.py +299 -0
- oscura/core/audit.py +457 -0
- oscura/core/backend_selector.py +405 -0
- oscura/core/cache.py +590 -0
- oscura/core/cancellation.py +439 -0
- oscura/core/confidence.py +225 -0
- oscura/core/config.py +506 -0
- oscura/core/correlation.py +216 -0
- oscura/core/cross_domain.py +422 -0
- oscura/core/debug.py +301 -0
- oscura/core/edge_cases.py +541 -0
- oscura/core/exceptions.py +535 -0
- oscura/core/gpu_backend.py +523 -0
- oscura/core/lazy.py +832 -0
- oscura/core/log_query.py +540 -0
- oscura/core/logging.py +931 -0
- oscura/core/logging_advanced.py +952 -0
- oscura/core/memoize.py +171 -0
- oscura/core/memory_check.py +274 -0
- oscura/core/memory_guard.py +290 -0
- oscura/core/memory_limits.py +336 -0
- oscura/core/memory_monitor.py +453 -0
- oscura/core/memory_progress.py +465 -0
- oscura/core/memory_warnings.py +315 -0
- oscura/core/numba_backend.py +362 -0
- oscura/core/performance.py +352 -0
- oscura/core/progress.py +524 -0
- oscura/core/provenance.py +358 -0
- oscura/core/results.py +331 -0
- oscura/core/types.py +504 -0
- oscura/core/uncertainty.py +383 -0
- oscura/discovery/__init__.py +52 -0
- oscura/discovery/anomaly_detector.py +672 -0
- oscura/discovery/auto_decoder.py +415 -0
- oscura/discovery/comparison.py +497 -0
- oscura/discovery/quality_validator.py +528 -0
- oscura/discovery/signal_detector.py +769 -0
- oscura/dsl/__init__.py +73 -0
- oscura/dsl/commands.py +246 -0
- oscura/dsl/interpreter.py +455 -0
- oscura/dsl/parser.py +689 -0
- oscura/dsl/repl.py +172 -0
- oscura/exceptions.py +59 -0
- oscura/exploratory/__init__.py +111 -0
- oscura/exploratory/error_recovery.py +642 -0
- oscura/exploratory/fuzzy.py +513 -0
- oscura/exploratory/fuzzy_advanced.py +786 -0
- oscura/exploratory/legacy.py +831 -0
- oscura/exploratory/parse.py +358 -0
- oscura/exploratory/recovery.py +275 -0
- oscura/exploratory/sync.py +382 -0
- oscura/exploratory/unknown.py +707 -0
- oscura/export/__init__.py +25 -0
- oscura/export/wireshark/README.md +265 -0
- oscura/export/wireshark/__init__.py +47 -0
- oscura/export/wireshark/generator.py +312 -0
- oscura/export/wireshark/lua_builder.py +159 -0
- oscura/export/wireshark/templates/dissector.lua.j2 +92 -0
- oscura/export/wireshark/type_mapping.py +165 -0
- oscura/export/wireshark/validator.py +105 -0
- oscura/exporters/__init__.py +94 -0
- oscura/exporters/csv.py +303 -0
- oscura/exporters/exporters.py +44 -0
- oscura/exporters/hdf5.py +219 -0
- oscura/exporters/html_export.py +701 -0
- oscura/exporters/json_export.py +291 -0
- oscura/exporters/markdown_export.py +367 -0
- oscura/exporters/matlab_export.py +354 -0
- oscura/exporters/npz_export.py +219 -0
- oscura/exporters/spice_export.py +210 -0
- oscura/extensibility/__init__.py +131 -0
- oscura/extensibility/docs.py +752 -0
- oscura/extensibility/extensions.py +1125 -0
- oscura/extensibility/logging.py +259 -0
- oscura/extensibility/measurements.py +485 -0
- oscura/extensibility/plugins.py +414 -0
- oscura/extensibility/registry.py +346 -0
- oscura/extensibility/templates.py +913 -0
- oscura/extensibility/validation.py +651 -0
- oscura/filtering/__init__.py +89 -0
- oscura/filtering/base.py +563 -0
- oscura/filtering/convenience.py +564 -0
- oscura/filtering/design.py +725 -0
- oscura/filtering/filters.py +32 -0
- oscura/filtering/introspection.py +605 -0
- oscura/guidance/__init__.py +24 -0
- oscura/guidance/recommender.py +429 -0
- oscura/guidance/wizard.py +518 -0
- oscura/inference/__init__.py +251 -0
- oscura/inference/active_learning/README.md +153 -0
- oscura/inference/active_learning/__init__.py +38 -0
- oscura/inference/active_learning/lstar.py +257 -0
- oscura/inference/active_learning/observation_table.py +230 -0
- oscura/inference/active_learning/oracle.py +78 -0
- oscura/inference/active_learning/teachers/__init__.py +15 -0
- oscura/inference/active_learning/teachers/simulator.py +192 -0
- oscura/inference/adaptive_tuning.py +453 -0
- oscura/inference/alignment.py +653 -0
- oscura/inference/bayesian.py +943 -0
- oscura/inference/binary.py +1016 -0
- oscura/inference/crc_reverse.py +711 -0
- oscura/inference/logic.py +288 -0
- oscura/inference/message_format.py +1305 -0
- oscura/inference/protocol.py +417 -0
- oscura/inference/protocol_dsl.py +1084 -0
- oscura/inference/protocol_library.py +1230 -0
- oscura/inference/sequences.py +809 -0
- oscura/inference/signal_intelligence.py +1509 -0
- oscura/inference/spectral.py +215 -0
- oscura/inference/state_machine.py +634 -0
- oscura/inference/stream.py +918 -0
- oscura/integrations/__init__.py +59 -0
- oscura/integrations/llm.py +1827 -0
- oscura/jupyter/__init__.py +32 -0
- oscura/jupyter/display.py +268 -0
- oscura/jupyter/magic.py +334 -0
- oscura/loaders/__init__.py +526 -0
- oscura/loaders/binary.py +69 -0
- oscura/loaders/configurable.py +1255 -0
- oscura/loaders/csv.py +26 -0
- oscura/loaders/csv_loader.py +473 -0
- oscura/loaders/hdf5.py +9 -0
- oscura/loaders/hdf5_loader.py +510 -0
- oscura/loaders/lazy.py +370 -0
- oscura/loaders/mmap_loader.py +583 -0
- oscura/loaders/numpy_loader.py +436 -0
- oscura/loaders/pcap.py +432 -0
- oscura/loaders/preprocessing.py +368 -0
- oscura/loaders/rigol.py +287 -0
- oscura/loaders/sigrok.py +321 -0
- oscura/loaders/tdms.py +367 -0
- oscura/loaders/tektronix.py +711 -0
- oscura/loaders/validation.py +584 -0
- oscura/loaders/vcd.py +464 -0
- oscura/loaders/wav.py +233 -0
- oscura/math/__init__.py +45 -0
- oscura/math/arithmetic.py +824 -0
- oscura/math/interpolation.py +413 -0
- oscura/onboarding/__init__.py +39 -0
- oscura/onboarding/help.py +498 -0
- oscura/onboarding/tutorials.py +405 -0
- oscura/onboarding/wizard.py +466 -0
- oscura/optimization/__init__.py +19 -0
- oscura/optimization/parallel.py +440 -0
- oscura/optimization/search.py +532 -0
- oscura/pipeline/__init__.py +43 -0
- oscura/pipeline/base.py +338 -0
- oscura/pipeline/composition.py +242 -0
- oscura/pipeline/parallel.py +448 -0
- oscura/pipeline/pipeline.py +375 -0
- oscura/pipeline/reverse_engineering.py +1119 -0
- oscura/plugins/__init__.py +122 -0
- oscura/plugins/base.py +272 -0
- oscura/plugins/cli.py +497 -0
- oscura/plugins/discovery.py +411 -0
- oscura/plugins/isolation.py +418 -0
- oscura/plugins/lifecycle.py +959 -0
- oscura/plugins/manager.py +493 -0
- oscura/plugins/registry.py +421 -0
- oscura/plugins/versioning.py +372 -0
- oscura/py.typed +0 -0
- oscura/quality/__init__.py +65 -0
- oscura/quality/ensemble.py +740 -0
- oscura/quality/explainer.py +338 -0
- oscura/quality/scoring.py +616 -0
- oscura/quality/warnings.py +456 -0
- oscura/reporting/__init__.py +248 -0
- oscura/reporting/advanced.py +1234 -0
- oscura/reporting/analyze.py +448 -0
- oscura/reporting/argument_preparer.py +596 -0
- oscura/reporting/auto_report.py +507 -0
- oscura/reporting/batch.py +615 -0
- oscura/reporting/chart_selection.py +223 -0
- oscura/reporting/comparison.py +330 -0
- oscura/reporting/config.py +615 -0
- oscura/reporting/content/__init__.py +39 -0
- oscura/reporting/content/executive.py +127 -0
- oscura/reporting/content/filtering.py +191 -0
- oscura/reporting/content/minimal.py +257 -0
- oscura/reporting/content/verbosity.py +162 -0
- oscura/reporting/core.py +508 -0
- oscura/reporting/core_formats/__init__.py +17 -0
- oscura/reporting/core_formats/multi_format.py +210 -0
- oscura/reporting/engine.py +836 -0
- oscura/reporting/export.py +366 -0
- oscura/reporting/formatting/__init__.py +129 -0
- oscura/reporting/formatting/emphasis.py +81 -0
- oscura/reporting/formatting/numbers.py +403 -0
- oscura/reporting/formatting/standards.py +55 -0
- oscura/reporting/formatting.py +466 -0
- oscura/reporting/html.py +578 -0
- oscura/reporting/index.py +590 -0
- oscura/reporting/multichannel.py +296 -0
- oscura/reporting/output.py +379 -0
- oscura/reporting/pdf.py +373 -0
- oscura/reporting/plots.py +731 -0
- oscura/reporting/pptx_export.py +360 -0
- oscura/reporting/renderers/__init__.py +11 -0
- oscura/reporting/renderers/pdf.py +94 -0
- oscura/reporting/sections.py +471 -0
- oscura/reporting/standards.py +680 -0
- oscura/reporting/summary_generator.py +368 -0
- oscura/reporting/tables.py +397 -0
- oscura/reporting/template_system.py +724 -0
- oscura/reporting/templates/__init__.py +15 -0
- oscura/reporting/templates/definition.py +205 -0
- oscura/reporting/templates/index.html +649 -0
- oscura/reporting/templates/index.md +173 -0
- oscura/schemas/__init__.py +158 -0
- oscura/schemas/bus_configuration.json +322 -0
- oscura/schemas/device_mapping.json +182 -0
- oscura/schemas/packet_format.json +418 -0
- oscura/schemas/protocol_definition.json +363 -0
- oscura/search/__init__.py +16 -0
- oscura/search/anomaly.py +292 -0
- oscura/search/context.py +149 -0
- oscura/search/pattern.py +160 -0
- oscura/session/__init__.py +34 -0
- oscura/session/annotations.py +289 -0
- oscura/session/history.py +313 -0
- oscura/session/session.py +445 -0
- oscura/streaming/__init__.py +43 -0
- oscura/streaming/chunked.py +611 -0
- oscura/streaming/progressive.py +393 -0
- oscura/streaming/realtime.py +622 -0
- oscura/testing/__init__.py +54 -0
- oscura/testing/synthetic.py +808 -0
- oscura/triggering/__init__.py +68 -0
- oscura/triggering/base.py +229 -0
- oscura/triggering/edge.py +353 -0
- oscura/triggering/pattern.py +344 -0
- oscura/triggering/pulse.py +581 -0
- oscura/triggering/window.py +453 -0
- oscura/ui/__init__.py +48 -0
- oscura/ui/formatters.py +526 -0
- oscura/ui/progressive_display.py +340 -0
- oscura/utils/__init__.py +99 -0
- oscura/utils/autodetect.py +338 -0
- oscura/utils/buffer.py +389 -0
- oscura/utils/lazy.py +407 -0
- oscura/utils/lazy_imports.py +147 -0
- oscura/utils/memory.py +836 -0
- oscura/utils/memory_advanced.py +1326 -0
- oscura/utils/memory_extensions.py +465 -0
- oscura/utils/progressive.py +352 -0
- oscura/utils/windowing.py +362 -0
- oscura/visualization/__init__.py +321 -0
- oscura/visualization/accessibility.py +526 -0
- oscura/visualization/annotations.py +374 -0
- oscura/visualization/axis_scaling.py +305 -0
- oscura/visualization/colors.py +453 -0
- oscura/visualization/digital.py +337 -0
- oscura/visualization/eye.py +420 -0
- oscura/visualization/histogram.py +281 -0
- oscura/visualization/interactive.py +858 -0
- oscura/visualization/jitter.py +702 -0
- oscura/visualization/keyboard.py +394 -0
- oscura/visualization/layout.py +365 -0
- oscura/visualization/optimization.py +1028 -0
- oscura/visualization/palettes.py +446 -0
- oscura/visualization/plot.py +92 -0
- oscura/visualization/power.py +290 -0
- oscura/visualization/power_extended.py +626 -0
- oscura/visualization/presets.py +467 -0
- oscura/visualization/protocols.py +932 -0
- oscura/visualization/render.py +207 -0
- oscura/visualization/rendering.py +444 -0
- oscura/visualization/reverse_engineering.py +791 -0
- oscura/visualization/signal_integrity.py +808 -0
- oscura/visualization/specialized.py +553 -0
- oscura/visualization/spectral.py +811 -0
- oscura/visualization/styles.py +381 -0
- oscura/visualization/thumbnails.py +311 -0
- oscura/visualization/time_axis.py +351 -0
- oscura/visualization/waveform.py +367 -0
- oscura/workflow/__init__.py +13 -0
- oscura/workflow/dag.py +377 -0
- oscura/workflows/__init__.py +58 -0
- oscura/workflows/compliance.py +280 -0
- oscura/workflows/digital.py +272 -0
- oscura/workflows/multi_trace.py +502 -0
- oscura/workflows/power.py +178 -0
- oscura/workflows/protocol.py +492 -0
- oscura/workflows/reverse_engineering.py +639 -0
- oscura/workflows/signal_integrity.py +227 -0
- oscura-0.1.1.dist-info/METADATA +300 -0
- oscura-0.1.1.dist-info/RECORD +463 -0
- oscura-0.1.1.dist-info/entry_points.txt +2 -0
- {oscura-0.0.1.dist-info → oscura-0.1.1.dist-info}/licenses/LICENSE +1 -1
- oscura-0.0.1.dist-info/METADATA +0 -63
- oscura-0.0.1.dist-info/RECORD +0 -5
- {oscura-0.0.1.dist-info → oscura-0.1.1.dist-info}/WHEEL +0 -0
oscura/core/log_query.py
ADDED
|
@@ -0,0 +1,540 @@
|
|
|
1
|
+
"""Log query and export functionality.
|
|
2
|
+
|
|
3
|
+
This module provides searchable log querying with filtering and export
|
|
4
|
+
capabilities for analysis and reporting.
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
Example:
|
|
8
|
+
>>> from oscura.core.log_query import LogQuery
|
|
9
|
+
>>> query = LogQuery()
|
|
10
|
+
>>> # Query last hour of ERROR logs
|
|
11
|
+
>>> from datetime import datetime, UTC, timedelta
|
|
12
|
+
>>> results = query.query_logs(
|
|
13
|
+
... start_time=datetime.now(UTC) - timedelta(hours=1),
|
|
14
|
+
... level="ERROR"
|
|
15
|
+
... )
|
|
16
|
+
>>> # Export to JSON
|
|
17
|
+
>>> query.export_logs(results, "errors.json", format="json")
|
|
18
|
+
|
|
19
|
+
References:
|
|
20
|
+
LOG-010: Searchable Log Query and Export
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
from __future__ import annotations
|
|
24
|
+
|
|
25
|
+
import csv
|
|
26
|
+
import json
|
|
27
|
+
import re
|
|
28
|
+
from dataclasses import asdict, dataclass
|
|
29
|
+
from pathlib import Path
|
|
30
|
+
from typing import TYPE_CHECKING, Any, Literal
|
|
31
|
+
|
|
32
|
+
from oscura.core.logging import format_timestamp
|
|
33
|
+
|
|
34
|
+
if TYPE_CHECKING:
|
|
35
|
+
from datetime import datetime
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@dataclass
|
|
39
|
+
class LogRecord:
|
|
40
|
+
"""Structured log record for querying.
|
|
41
|
+
|
|
42
|
+
Attributes:
|
|
43
|
+
timestamp: ISO 8601 timestamp of the log entry.
|
|
44
|
+
level: Log level (DEBUG, INFO, WARNING, ERROR, CRITICAL).
|
|
45
|
+
module: Logger name/module.
|
|
46
|
+
message: Log message.
|
|
47
|
+
correlation_id: Optional correlation ID for tracing.
|
|
48
|
+
metadata: Additional metadata fields.
|
|
49
|
+
|
|
50
|
+
References:
|
|
51
|
+
LOG-010: Searchable Log Query
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
timestamp: str
|
|
55
|
+
level: str
|
|
56
|
+
module: str
|
|
57
|
+
message: str
|
|
58
|
+
correlation_id: str | None = None
|
|
59
|
+
metadata: dict[str, Any] | None = None
|
|
60
|
+
|
|
61
|
+
def to_dict(self) -> dict[str, Any]:
|
|
62
|
+
"""Convert log record to dictionary.
|
|
63
|
+
|
|
64
|
+
Returns:
|
|
65
|
+
Dictionary representation of the log record.
|
|
66
|
+
"""
|
|
67
|
+
result = asdict(self)
|
|
68
|
+
if result["metadata"] is None:
|
|
69
|
+
result["metadata"] = {}
|
|
70
|
+
return result
|
|
71
|
+
|
|
72
|
+
@classmethod
|
|
73
|
+
def from_dict(cls, data: dict[str, Any]) -> LogRecord:
|
|
74
|
+
"""Create log record from dictionary.
|
|
75
|
+
|
|
76
|
+
Args:
|
|
77
|
+
data: Dictionary containing log record data.
|
|
78
|
+
|
|
79
|
+
Returns:
|
|
80
|
+
LogRecord instance.
|
|
81
|
+
"""
|
|
82
|
+
return cls(
|
|
83
|
+
timestamp=data["timestamp"],
|
|
84
|
+
level=data["level"],
|
|
85
|
+
module=data["module"],
|
|
86
|
+
message=data["message"],
|
|
87
|
+
correlation_id=data.get("correlation_id"),
|
|
88
|
+
metadata=data.get("metadata"),
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
class LogQuery:
|
|
93
|
+
"""Query and filter log records from various sources.
|
|
94
|
+
|
|
95
|
+
Provides structured querying of logs with filtering by timestamp,
|
|
96
|
+
level, module, correlation ID, and message patterns. Supports
|
|
97
|
+
pagination and multiple export formats.
|
|
98
|
+
|
|
99
|
+
Example:
|
|
100
|
+
>>> query = LogQuery()
|
|
101
|
+
>>> # Load logs from file
|
|
102
|
+
>>> query.load_from_file("oscura.log")
|
|
103
|
+
>>> # Query with filters
|
|
104
|
+
>>> results = query.query_logs(
|
|
105
|
+
... level="ERROR",
|
|
106
|
+
... module_pattern="oscura.loaders.*"
|
|
107
|
+
... )
|
|
108
|
+
>>> # Export filtered results
|
|
109
|
+
>>> query.export_logs(results, "filtered.csv", format="csv")
|
|
110
|
+
|
|
111
|
+
References:
|
|
112
|
+
LOG-010: Searchable Log Query and Export
|
|
113
|
+
"""
|
|
114
|
+
|
|
115
|
+
def __init__(self): # type: ignore[no-untyped-def]
|
|
116
|
+
"""Initialize log query engine."""
|
|
117
|
+
self._records: list[LogRecord] = []
|
|
118
|
+
|
|
119
|
+
def load_from_file(self, path: str, format: Literal["json", "text"] = "text") -> int:
|
|
120
|
+
"""Load log records from file.
|
|
121
|
+
|
|
122
|
+
Args:
|
|
123
|
+
path: Path to log file.
|
|
124
|
+
format: File format (json for JSON lines, text for plain text).
|
|
125
|
+
|
|
126
|
+
Returns:
|
|
127
|
+
Number of records loaded.
|
|
128
|
+
|
|
129
|
+
Raises:
|
|
130
|
+
FileNotFoundError: If log file does not exist.
|
|
131
|
+
ValueError: If format is not supported.
|
|
132
|
+
|
|
133
|
+
Example:
|
|
134
|
+
>>> query = LogQuery()
|
|
135
|
+
>>> count = query.load_from_file("logs.json", format="json")
|
|
136
|
+
>>> print(f"Loaded {count} records")
|
|
137
|
+
|
|
138
|
+
References:
|
|
139
|
+
LOG-010: Searchable Log Query
|
|
140
|
+
"""
|
|
141
|
+
path_obj = Path(path)
|
|
142
|
+
if not path_obj.exists():
|
|
143
|
+
raise FileNotFoundError(f"Log file not found: {path}")
|
|
144
|
+
|
|
145
|
+
if format == "json":
|
|
146
|
+
return self._load_json_lines(path_obj)
|
|
147
|
+
elif format == "text":
|
|
148
|
+
return self._load_text(path_obj)
|
|
149
|
+
else:
|
|
150
|
+
raise ValueError(f"Unsupported format: {format}")
|
|
151
|
+
|
|
152
|
+
def add_record(self, record: LogRecord) -> None:
|
|
153
|
+
"""Add a log record to the query index.
|
|
154
|
+
|
|
155
|
+
Args:
|
|
156
|
+
record: LogRecord to add.
|
|
157
|
+
|
|
158
|
+
Example:
|
|
159
|
+
>>> query = LogQuery()
|
|
160
|
+
>>> record = LogRecord(
|
|
161
|
+
... timestamp="2025-12-21T10:00:00.000000Z",
|
|
162
|
+
... level="INFO",
|
|
163
|
+
... module="oscura.test",
|
|
164
|
+
... message="Test message"
|
|
165
|
+
... )
|
|
166
|
+
>>> query.add_record(record)
|
|
167
|
+
"""
|
|
168
|
+
self._records.append(record)
|
|
169
|
+
|
|
170
|
+
def query_logs(
|
|
171
|
+
self,
|
|
172
|
+
*,
|
|
173
|
+
start_time: datetime | None = None,
|
|
174
|
+
end_time: datetime | None = None,
|
|
175
|
+
level: str | None = None,
|
|
176
|
+
module: str | None = None,
|
|
177
|
+
module_pattern: str | None = None,
|
|
178
|
+
correlation_id: str | None = None,
|
|
179
|
+
message_pattern: str | None = None,
|
|
180
|
+
limit: int | None = None,
|
|
181
|
+
offset: int = 0,
|
|
182
|
+
) -> list[LogRecord]:
|
|
183
|
+
"""Query log records with filtering.
|
|
184
|
+
|
|
185
|
+
Args:
|
|
186
|
+
start_time: Return only logs after this time (UTC).
|
|
187
|
+
end_time: Return only logs before this time (UTC).
|
|
188
|
+
level: Filter by exact log level.
|
|
189
|
+
module: Filter by exact module name.
|
|
190
|
+
module_pattern: Filter by module name pattern (glob-style, e.g., "oscura.loaders.*").
|
|
191
|
+
correlation_id: Filter by correlation ID.
|
|
192
|
+
message_pattern: Filter by message regex pattern.
|
|
193
|
+
limit: Maximum number of results to return.
|
|
194
|
+
offset: Number of results to skip (for pagination).
|
|
195
|
+
|
|
196
|
+
Returns:
|
|
197
|
+
List of matching LogRecord objects.
|
|
198
|
+
|
|
199
|
+
Example:
|
|
200
|
+
>>> from datetime import datetime, UTC, timedelta
|
|
201
|
+
>>> query = LogQuery()
|
|
202
|
+
>>> # Last hour of errors
|
|
203
|
+
>>> results = query.query_logs(
|
|
204
|
+
... start_time=datetime.now(UTC) - timedelta(hours=1),
|
|
205
|
+
... level="ERROR"
|
|
206
|
+
... )
|
|
207
|
+
>>> # Specific module with pattern
|
|
208
|
+
>>> results = query.query_logs(
|
|
209
|
+
... module_pattern="oscura.analyzers.*",
|
|
210
|
+
... message_pattern="FFT.*failed"
|
|
211
|
+
... )
|
|
212
|
+
>>> # Paginated results
|
|
213
|
+
>>> page_1 = query.query_logs(limit=100, offset=0)
|
|
214
|
+
>>> page_2 = query.query_logs(limit=100, offset=100)
|
|
215
|
+
|
|
216
|
+
References:
|
|
217
|
+
LOG-010: Searchable Log Query and Export
|
|
218
|
+
"""
|
|
219
|
+
results = self._records.copy()
|
|
220
|
+
|
|
221
|
+
# Filter by timestamp range
|
|
222
|
+
if start_time is not None:
|
|
223
|
+
start_str = format_timestamp(start_time, format="iso8601")
|
|
224
|
+
results = [r for r in results if r.timestamp >= start_str]
|
|
225
|
+
|
|
226
|
+
if end_time is not None:
|
|
227
|
+
end_str = format_timestamp(end_time, format="iso8601")
|
|
228
|
+
results = [r for r in results if r.timestamp <= end_str]
|
|
229
|
+
|
|
230
|
+
# Filter by log level
|
|
231
|
+
if level is not None:
|
|
232
|
+
results = [r for r in results if r.level == level.upper()]
|
|
233
|
+
|
|
234
|
+
# Filter by module
|
|
235
|
+
if module is not None:
|
|
236
|
+
results = [r for r in results if r.module == module]
|
|
237
|
+
|
|
238
|
+
# Filter by module pattern
|
|
239
|
+
if module_pattern is not None:
|
|
240
|
+
# Convert glob pattern to regex
|
|
241
|
+
pattern = module_pattern.replace(".", r"\.").replace("*", ".*")
|
|
242
|
+
regex = re.compile(f"^{pattern}$")
|
|
243
|
+
results = [r for r in results if regex.match(r.module)]
|
|
244
|
+
|
|
245
|
+
# Filter by correlation ID
|
|
246
|
+
if correlation_id is not None:
|
|
247
|
+
results = [r for r in results if r.correlation_id == correlation_id]
|
|
248
|
+
|
|
249
|
+
# Filter by message pattern
|
|
250
|
+
if message_pattern is not None:
|
|
251
|
+
regex = re.compile(message_pattern)
|
|
252
|
+
results = [r for r in results if regex.search(r.message)]
|
|
253
|
+
|
|
254
|
+
# Apply pagination
|
|
255
|
+
if offset > 0:
|
|
256
|
+
results = results[offset:]
|
|
257
|
+
if limit is not None:
|
|
258
|
+
results = results[:limit]
|
|
259
|
+
|
|
260
|
+
return results
|
|
261
|
+
|
|
262
|
+
def export_logs(
|
|
263
|
+
self,
|
|
264
|
+
records: list[LogRecord],
|
|
265
|
+
path: str,
|
|
266
|
+
format: Literal["json", "csv", "text"] = "json",
|
|
267
|
+
) -> None:
|
|
268
|
+
"""Export log records to file.
|
|
269
|
+
|
|
270
|
+
Args:
|
|
271
|
+
records: List of LogRecord objects to export.
|
|
272
|
+
path: Output file path.
|
|
273
|
+
format: Export format (json, csv, or text).
|
|
274
|
+
|
|
275
|
+
Raises:
|
|
276
|
+
ValueError: If format is not supported.
|
|
277
|
+
|
|
278
|
+
Example:
|
|
279
|
+
>>> query = LogQuery()
|
|
280
|
+
>>> results = query.query_logs(level="ERROR")
|
|
281
|
+
>>> query.export_logs(results, "errors.json", format="json")
|
|
282
|
+
>>> query.export_logs(results, "errors.csv", format="csv")
|
|
283
|
+
>>> query.export_logs(results, "errors.txt", format="text")
|
|
284
|
+
|
|
285
|
+
References:
|
|
286
|
+
LOG-010: Searchable Log Query and Export
|
|
287
|
+
"""
|
|
288
|
+
path_obj = Path(path)
|
|
289
|
+
path_obj.parent.mkdir(parents=True, exist_ok=True)
|
|
290
|
+
|
|
291
|
+
if format == "json":
|
|
292
|
+
self._export_json(records, path_obj)
|
|
293
|
+
elif format == "csv":
|
|
294
|
+
self._export_csv(records, path_obj)
|
|
295
|
+
elif format == "text":
|
|
296
|
+
self._export_text(records, path_obj)
|
|
297
|
+
else:
|
|
298
|
+
raise ValueError(f"Unsupported export format: {format}")
|
|
299
|
+
|
|
300
|
+
def get_statistics(self) -> dict[str, Any]:
|
|
301
|
+
"""Get statistics about loaded log records.
|
|
302
|
+
|
|
303
|
+
Returns:
|
|
304
|
+
Dictionary with statistics:
|
|
305
|
+
- total: Total number of records
|
|
306
|
+
- by_level: Count by log level
|
|
307
|
+
- by_module: Count by module
|
|
308
|
+
- time_range: Earliest and latest timestamps
|
|
309
|
+
|
|
310
|
+
Example:
|
|
311
|
+
>>> query = LogQuery()
|
|
312
|
+
>>> query.load_from_file("logs.json")
|
|
313
|
+
>>> stats = query.get_statistics()
|
|
314
|
+
>>> print(f"Total logs: {stats['total']}")
|
|
315
|
+
>>> print(f"Errors: {stats['by_level'].get('ERROR', 0)}")
|
|
316
|
+
|
|
317
|
+
References:
|
|
318
|
+
LOG-010: Log Query
|
|
319
|
+
"""
|
|
320
|
+
if not self._records:
|
|
321
|
+
return {
|
|
322
|
+
"total": 0,
|
|
323
|
+
"by_level": {},
|
|
324
|
+
"by_module": {},
|
|
325
|
+
"time_range": None,
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
from collections import Counter
|
|
329
|
+
|
|
330
|
+
level_counts = Counter(r.level for r in self._records)
|
|
331
|
+
module_counts = Counter(r.module for r in self._records)
|
|
332
|
+
|
|
333
|
+
timestamps = sorted(r.timestamp for r in self._records)
|
|
334
|
+
time_range = {
|
|
335
|
+
"earliest": timestamps[0],
|
|
336
|
+
"latest": timestamps[-1],
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
return {
|
|
340
|
+
"total": len(self._records),
|
|
341
|
+
"by_level": dict(level_counts),
|
|
342
|
+
"by_module": dict(module_counts.most_common(20)),
|
|
343
|
+
"time_range": time_range,
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
def clear(self) -> None:
|
|
347
|
+
"""Clear all loaded log records.
|
|
348
|
+
|
|
349
|
+
Example:
|
|
350
|
+
>>> query = LogQuery()
|
|
351
|
+
>>> query.clear()
|
|
352
|
+
"""
|
|
353
|
+
self._records.clear()
|
|
354
|
+
|
|
355
|
+
def _load_json_lines(self, path: Path) -> int:
|
|
356
|
+
"""Load JSON lines format logs.
|
|
357
|
+
|
|
358
|
+
Args:
|
|
359
|
+
path: Path to JSON lines file.
|
|
360
|
+
|
|
361
|
+
Returns:
|
|
362
|
+
Number of records loaded.
|
|
363
|
+
"""
|
|
364
|
+
count = 0
|
|
365
|
+
with open(path, encoding="utf-8") as f:
|
|
366
|
+
for line in f:
|
|
367
|
+
line = line.strip()
|
|
368
|
+
if not line:
|
|
369
|
+
continue
|
|
370
|
+
try:
|
|
371
|
+
data = json.loads(line)
|
|
372
|
+
record = LogRecord(
|
|
373
|
+
timestamp=data.get("timestamp", ""),
|
|
374
|
+
level=data.get("level", "INFO"),
|
|
375
|
+
module=data.get("module", "unknown"),
|
|
376
|
+
message=data.get("message", ""),
|
|
377
|
+
correlation_id=data.get("correlation_id"),
|
|
378
|
+
metadata={
|
|
379
|
+
k: v
|
|
380
|
+
for k, v in data.items()
|
|
381
|
+
if k
|
|
382
|
+
not in ("timestamp", "level", "module", "message", "correlation_id")
|
|
383
|
+
},
|
|
384
|
+
)
|
|
385
|
+
self._records.append(record)
|
|
386
|
+
count += 1
|
|
387
|
+
except (json.JSONDecodeError, KeyError):
|
|
388
|
+
# Skip malformed lines
|
|
389
|
+
continue
|
|
390
|
+
return count
|
|
391
|
+
|
|
392
|
+
def _load_text(self, path: Path) -> int:
|
|
393
|
+
"""Load plain text format logs.
|
|
394
|
+
|
|
395
|
+
Attempts to parse common log formats.
|
|
396
|
+
|
|
397
|
+
Args:
|
|
398
|
+
path: Path to text log file.
|
|
399
|
+
|
|
400
|
+
Returns:
|
|
401
|
+
Number of records loaded.
|
|
402
|
+
"""
|
|
403
|
+
count = 0
|
|
404
|
+
with open(path, encoding="utf-8") as f:
|
|
405
|
+
for line in f:
|
|
406
|
+
line = line.strip()
|
|
407
|
+
if not line:
|
|
408
|
+
continue
|
|
409
|
+
|
|
410
|
+
# Try to parse common format: TIMESTAMP [LEVEL] MODULE: MESSAGE
|
|
411
|
+
match = re.match(
|
|
412
|
+
r"^(\S+)\s+\[(\w+)\]\s+(\S+):\s+(.*)$",
|
|
413
|
+
line,
|
|
414
|
+
)
|
|
415
|
+
if match:
|
|
416
|
+
timestamp, level, module, message = match.groups()
|
|
417
|
+
record = LogRecord(
|
|
418
|
+
timestamp=timestamp,
|
|
419
|
+
level=level,
|
|
420
|
+
module=module,
|
|
421
|
+
message=message,
|
|
422
|
+
)
|
|
423
|
+
self._records.append(record)
|
|
424
|
+
count += 1
|
|
425
|
+
|
|
426
|
+
return count
|
|
427
|
+
|
|
428
|
+
def _export_json(self, records: list[LogRecord], path: Path) -> None:
|
|
429
|
+
"""Export records as JSON.
|
|
430
|
+
|
|
431
|
+
Args:
|
|
432
|
+
records: Records to export.
|
|
433
|
+
path: Output path.
|
|
434
|
+
"""
|
|
435
|
+
with open(path, "w", encoding="utf-8") as f:
|
|
436
|
+
json.dump(
|
|
437
|
+
[r.to_dict() for r in records],
|
|
438
|
+
f,
|
|
439
|
+
indent=2,
|
|
440
|
+
default=str,
|
|
441
|
+
)
|
|
442
|
+
|
|
443
|
+
def _export_csv(self, records: list[LogRecord], path: Path) -> None:
|
|
444
|
+
"""Export records as CSV.
|
|
445
|
+
|
|
446
|
+
Args:
|
|
447
|
+
records: Records to export.
|
|
448
|
+
path: Output path.
|
|
449
|
+
"""
|
|
450
|
+
if not records:
|
|
451
|
+
return
|
|
452
|
+
|
|
453
|
+
with open(path, "w", newline="", encoding="utf-8") as f:
|
|
454
|
+
fieldnames = ["timestamp", "level", "module", "message", "correlation_id"]
|
|
455
|
+
writer = csv.DictWriter(f, fieldnames=fieldnames)
|
|
456
|
+
writer.writeheader()
|
|
457
|
+
|
|
458
|
+
for record in records:
|
|
459
|
+
writer.writerow(
|
|
460
|
+
{
|
|
461
|
+
"timestamp": record.timestamp,
|
|
462
|
+
"level": record.level,
|
|
463
|
+
"module": record.module,
|
|
464
|
+
"message": record.message,
|
|
465
|
+
"correlation_id": record.correlation_id or "",
|
|
466
|
+
}
|
|
467
|
+
)
|
|
468
|
+
|
|
469
|
+
def _export_text(self, records: list[LogRecord], path: Path) -> None:
|
|
470
|
+
"""Export records as plain text.
|
|
471
|
+
|
|
472
|
+
Args:
|
|
473
|
+
records: Records to export.
|
|
474
|
+
path: Output path.
|
|
475
|
+
"""
|
|
476
|
+
with open(path, "w", encoding="utf-8") as f:
|
|
477
|
+
for record in records:
|
|
478
|
+
line = f"{record.timestamp} [{record.level}] {record.module}: {record.message}"
|
|
479
|
+
if record.correlation_id:
|
|
480
|
+
line += f" [corr_id={record.correlation_id}]"
|
|
481
|
+
f.write(line + "\n")
|
|
482
|
+
|
|
483
|
+
|
|
484
|
+
def query_logs(
|
|
485
|
+
log_file: str,
|
|
486
|
+
*,
|
|
487
|
+
start_time: datetime | None = None,
|
|
488
|
+
end_time: datetime | None = None,
|
|
489
|
+
level: str | None = None,
|
|
490
|
+
module: str | None = None,
|
|
491
|
+
correlation_id: str | None = None,
|
|
492
|
+
message_pattern: str | None = None,
|
|
493
|
+
limit: int | None = None,
|
|
494
|
+
) -> list[LogRecord]:
|
|
495
|
+
"""Convenience function to query logs from a file.
|
|
496
|
+
|
|
497
|
+
Args:
|
|
498
|
+
log_file: Path to log file.
|
|
499
|
+
start_time: Filter by start time (UTC).
|
|
500
|
+
end_time: Filter by end time (UTC).
|
|
501
|
+
level: Filter by log level.
|
|
502
|
+
module: Filter by module name.
|
|
503
|
+
correlation_id: Filter by correlation ID.
|
|
504
|
+
message_pattern: Filter by message regex pattern.
|
|
505
|
+
limit: Maximum number of results.
|
|
506
|
+
|
|
507
|
+
Returns:
|
|
508
|
+
List of matching LogRecord objects.
|
|
509
|
+
|
|
510
|
+
Example:
|
|
511
|
+
>>> from datetime import datetime, UTC, timedelta
|
|
512
|
+
>>> from oscura.core.log_query import query_logs
|
|
513
|
+
>>> # Query last hour of errors
|
|
514
|
+
>>> results = query_logs(
|
|
515
|
+
... "oscura.log",
|
|
516
|
+
... start_time=datetime.now(UTC) - timedelta(hours=1),
|
|
517
|
+
... level="ERROR"
|
|
518
|
+
... )
|
|
519
|
+
|
|
520
|
+
References:
|
|
521
|
+
LOG-010: Searchable Log Query and Export
|
|
522
|
+
"""
|
|
523
|
+
query = LogQuery() # type: ignore[no-untyped-call]
|
|
524
|
+
query.load_from_file(log_file, format="json" if log_file.endswith(".json") else "text")
|
|
525
|
+
return query.query_logs(
|
|
526
|
+
start_time=start_time,
|
|
527
|
+
end_time=end_time,
|
|
528
|
+
level=level,
|
|
529
|
+
module=module,
|
|
530
|
+
correlation_id=correlation_id,
|
|
531
|
+
message_pattern=message_pattern,
|
|
532
|
+
limit=limit,
|
|
533
|
+
)
|
|
534
|
+
|
|
535
|
+
|
|
536
|
+
__all__ = [
|
|
537
|
+
"LogQuery",
|
|
538
|
+
"LogRecord",
|
|
539
|
+
"query_logs",
|
|
540
|
+
]
|