diff --git a/compile_protos.sh b/compile_protos.sh index affd0fe..214f9cd 100755 --- a/compile_protos.sh +++ b/compile_protos.sh @@ -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 diff --git a/examples/standard-logging/README.md b/examples/standard-logging/README.md new file mode 100644 index 0000000..4a2fe7f --- /dev/null +++ b/examples/standard-logging/README.md @@ -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") +``` diff --git a/examples/standard-logging/pyproject.toml b/examples/standard-logging/pyproject.toml new file mode 100644 index 0000000..94ccafd --- /dev/null +++ b/examples/standard-logging/pyproject.toml @@ -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" diff --git a/examples/standard-logging/standard_logging_example.py b/examples/standard-logging/standard_logging_example.py new file mode 100644 index 0000000..fb8acd1 --- /dev/null +++ b/examples/standard-logging/standard_logging_example.py @@ -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() diff --git a/examples/structlog/README.md b/examples/structlog/README.md new file mode 100644 index 0000000..4185916 --- /dev/null +++ b/examples/structlog/README.md @@ -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"]} +``` diff --git a/examples/structlog/pyproject.toml b/examples/structlog/pyproject.toml new file mode 100644 index 0000000..28ba7c9 --- /dev/null +++ b/examples/structlog/pyproject.toml @@ -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" diff --git a/examples/structlog/structlog_example.py b/examples/structlog/structlog_example.py new file mode 100644 index 0000000..155652a --- /dev/null +++ b/examples/structlog/structlog_example.py @@ -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() diff --git a/prefab_pb2.py b/prefab_pb2.py index 038a2c0..caf4f96 100644 --- a/prefab_pb2.py +++ b/prefab_pb2.py @@ -13,7 +13,7 @@ -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x0cprefab.proto\x12\x06prefab\"{\n\x14\x43onfigServicePointer\x12\x1d\n\nproject_id\x18\x01 \x01(\x03R\tprojectId\x12\x1e\n\x0bstart_at_id\x18\x02 \x01(\x03R\tstartAtId\x12$\n\x0eproject_env_id\x18\x03 \x01(\x03R\x0cprojectEnvId\"\xd1\x05\n\x0b\x43onfigValue\x12\x12\n\x03int\x18\x01 \x01(\x03H\x00R\x03int\x12\x18\n\x06string\x18\x02 \x01(\tH\x00R\x06string\x12\x16\n\x05\x62ytes\x18\x03 \x01(\x0cH\x00R\x05\x62ytes\x12\x18\n\x06\x64ouble\x18\x04 \x01(\x01H\x00R\x06\x64ouble\x12\x14\n\x04\x62ool\x18\x05 \x01(\x08H\x00R\x04\x62ool\x12\x41\n\x0fweighted_values\x18\x06 \x01(\x0b\x32\x16.prefab.WeightedValuesH\x00R\x0eweightedValues\x12\x44\n\x10limit_definition\x18\x07 \x01(\x0b\x32\x17.prefab.LimitDefinitionH\x00R\x0flimitDefinition\x12/\n\tlog_level\x18\t \x01(\x0e\x32\x10.prefab.LogLevelH\x00R\x08logLevel\x12\x35\n\x0bstring_list\x18\n \x01(\x0b\x32\x12.prefab.StringListH\x00R\nstringList\x12/\n\tint_range\x18\x0b \x01(\x0b\x32\x10.prefab.IntRangeH\x00R\x08intRange\x12.\n\x08provided\x18\x0c \x01(\x0b\x32\x10.prefab.ProvidedH\x00R\x08provided\x12\x31\n\x08\x64uration\x18\x0f \x01(\x0b\x32\x13.prefab.IsoDurationH\x00R\x08\x64uration\x12\"\n\x04json\x18\x10 \x01(\x0b\x32\x0c.prefab.JsonH\x00R\x04json\x12(\n\x06schema\x18\x11 \x01(\x0b\x32\x0e.prefab.SchemaH\x00R\x06schema\x12\'\n\x0c\x63onfidential\x18\r \x01(\x08H\x01R\x0c\x63onfidential\x88\x01\x01\x12&\n\x0c\x64\x65\x63rypt_with\x18\x0e \x01(\tH\x02R\x0b\x64\x65\x63ryptWith\x88\x01\x01\x42\x06\n\x04typeB\x0f\n\r_confidentialB\x0f\n\r_decrypt_with\"\x1a\n\x04Json\x12\x12\n\x04json\x18\x01 \x01(\tR\x04json\"-\n\x0bIsoDuration\x12\x1e\n\ndefinition\x18\x01 \x01(\tR\ndefinition\"r\n\x08Provided\x12\x33\n\x06source\x18\x01 \x01(\x0e\x32\x16.prefab.ProvidedSourceH\x00R\x06source\x88\x01\x01\x12\x1b\n\x06lookup\x18\x02 \x01(\tH\x01R\x06lookup\x88\x01\x01\x42\t\n\x07_sourceB\t\n\x07_lookup\"N\n\x08IntRange\x12\x19\n\x05start\x18\x01 \x01(\x03H\x00R\x05start\x88\x01\x01\x12\x15\n\x03\x65nd\x18\x02 \x01(\x03H\x01R\x03\x65nd\x88\x01\x01\x42\x08\n\x06_startB\x06\n\x04_end\"$\n\nStringList\x12\x16\n\x06values\x18\x01 \x03(\tR\x06values\"R\n\rWeightedValue\x12\x16\n\x06weight\x18\x01 \x01(\x05R\x06weight\x12)\n\x05value\x18\x02 \x01(\x0b\x32\x13.prefab.ConfigValueR\x05value\"\xa2\x01\n\x0eWeightedValues\x12>\n\x0fweighted_values\x18\x01 \x03(\x0b\x32\x15.prefab.WeightedValueR\x0eweightedValues\x12\x36\n\x15hash_by_property_name\x18\x02 \x01(\tH\x00R\x12hashByPropertyName\x88\x01\x01\x42\x18\n\x16_hash_by_property_name\"g\n\x0e\x41piKeyMetadata\x12\x1a\n\x06key_id\x18\x01 \x01(\tH\x00R\x05keyId\x88\x01\x01\x12\x1c\n\x07user_id\x18\x03 \x01(\tH\x01R\x06userId\x88\x01\x01\x42\t\n\x07_key_idB\n\n\x08_user_idJ\x04\x08\x02\x10\x03\"\xea\x02\n\x07\x43onfigs\x12(\n\x07\x63onfigs\x18\x01 \x03(\x0b\x32\x0e.prefab.ConfigR\x07\x63onfigs\x12R\n\x16\x63onfig_service_pointer\x18\x02 \x01(\x0b\x32\x1c.prefab.ConfigServicePointerR\x14\x63onfigServicePointer\x12\x44\n\x0f\x61pikey_metadata\x18\x03 \x01(\x0b\x32\x16.prefab.ApiKeyMetadataH\x00R\x0e\x61pikeyMetadata\x88\x01\x01\x12@\n\x0f\x64\x65\x66\x61ult_context\x18\x04 \x01(\x0b\x32\x12.prefab.ContextSetH\x01R\x0e\x64\x65\x66\x61ultContext\x88\x01\x01\x12\"\n\nkeep_alive\x18\x05 \x01(\x08H\x02R\tkeepAlive\x88\x01\x01\x42\x12\n\x10_apikey_metadataB\x12\n\x10_default_contextB\r\n\x0b_keep_alive\"\x96\x05\n\x06\x43onfig\x12\x0e\n\x02id\x18\x01 \x01(\x03R\x02id\x12\x1d\n\nproject_id\x18\x02 \x01(\x03R\tprojectId\x12\x10\n\x03key\x18\x03 \x01(\tR\x03key\x12\x30\n\nchanged_by\x18\x04 \x01(\x0b\x32\x11.prefab.ChangedByR\tchangedBy\x12%\n\x04rows\x18\x05 \x03(\x0b\x32\x11.prefab.ConfigRowR\x04rows\x12>\n\x10\x61llowable_values\x18\x06 \x03(\x0b\x32\x13.prefab.ConfigValueR\x0f\x61llowableValues\x12\x33\n\x0b\x63onfig_type\x18\x07 \x01(\x0e\x32\x12.prefab.ConfigTypeR\nconfigType\x12\x1e\n\x08\x64raft_id\x18\x08 \x01(\x03H\x00R\x07\x64raftId\x88\x01\x01\x12\x37\n\nvalue_type\x18\t \x01(\x0e\x32\x18.prefab.Config.ValueTypeR\tvalueType\x12+\n\x12send_to_client_sdk\x18\n \x01(\x08R\x0fsendToClientSdk\x12\"\n\nschema_key\x18\x0b \x01(\tH\x01R\tschemaKey\x88\x01\x01\"\xb6\x01\n\tValueType\x12\x16\n\x12NOT_SET_VALUE_TYPE\x10\x00\x12\x07\n\x03INT\x10\x01\x12\n\n\x06STRING\x10\x02\x12\t\n\x05\x42YTES\x10\x03\x12\n\n\x06\x44OUBLE\x10\x04\x12\x08\n\x04\x42OOL\x10\x05\x12\x14\n\x10LIMIT_DEFINITION\x10\x07\x12\r\n\tLOG_LEVEL\x10\t\x12\x0f\n\x0bSTRING_LIST\x10\n\x12\r\n\tINT_RANGE\x10\x0b\x12\x0c\n\x08\x44URATION\x10\x0c\x12\x08\n\x04JSON\x10\rB\x0b\n\t_draft_idB\r\n\x0b_schema_key\"X\n\tChangedBy\x12\x17\n\x07user_id\x18\x01 \x01(\x03R\x06userId\x12\x14\n\x05\x65mail\x18\x02 \x01(\tR\x05\x65mail\x12\x1c\n\napi_key_id\x18\x03 \x01(\tR\x08\x61piKeyId\"\x92\x02\n\tConfigRow\x12)\n\x0eproject_env_id\x18\x01 \x01(\x03H\x00R\x0cprojectEnvId\x88\x01\x01\x12\x30\n\x06values\x18\x02 \x03(\x0b\x32\x18.prefab.ConditionalValueR\x06values\x12\x41\n\nproperties\x18\x03 \x03(\x0b\x32!.prefab.ConfigRow.PropertiesEntryR\nproperties\x1aR\n\x0fPropertiesEntry\x12\x10\n\x03key\x18\x01 \x01(\tR\x03key\x12)\n\x05value\x18\x02 \x01(\x0b\x32\x13.prefab.ConfigValueR\x05value:\x02\x38\x01\x42\x11\n\x0f_project_env_id\"l\n\x10\x43onditionalValue\x12-\n\x08\x63riteria\x18\x01 \x03(\x0b\x32\x11.prefab.CriterionR\x08\x63riteria\x12)\n\x05value\x18\x02 \x01(\x0b\x32\x13.prefab.ConfigValueR\x05value\"\xbc\x06\n\tCriterion\x12#\n\rproperty_name\x18\x01 \x01(\tR\x0cpropertyName\x12?\n\x08operator\x18\x02 \x01(\x0e\x32#.prefab.Criterion.CriterionOperatorR\x08operator\x12\x39\n\x0evalue_to_match\x18\x03 \x01(\x0b\x32\x13.prefab.ConfigValueR\x0cvalueToMatch\"\x8d\x05\n\x11\x43riterionOperator\x12\x0b\n\x07NOT_SET\x10\x00\x12\x11\n\rLOOKUP_KEY_IN\x10\x01\x12\x15\n\x11LOOKUP_KEY_NOT_IN\x10\x02\x12\n\n\x06IN_SEG\x10\x03\x12\x0e\n\nNOT_IN_SEG\x10\x04\x12\x0f\n\x0b\x41LWAYS_TRUE\x10\x05\x12\x12\n\x0ePROP_IS_ONE_OF\x10\x06\x12\x16\n\x12PROP_IS_NOT_ONE_OF\x10\x07\x12\x19\n\x15PROP_ENDS_WITH_ONE_OF\x10\x08\x12!\n\x1dPROP_DOES_NOT_END_WITH_ONE_OF\x10\t\x12\x16\n\x12HIERARCHICAL_MATCH\x10\n\x12\x10\n\x0cIN_INT_RANGE\x10\x0b\x12\x1b\n\x17PROP_STARTS_WITH_ONE_OF\x10\x0c\x12#\n\x1fPROP_DOES_NOT_START_WITH_ONE_OF\x10\r\x12\x18\n\x14PROP_CONTAINS_ONE_OF\x10\x0e\x12 \n\x1cPROP_DOES_NOT_CONTAIN_ONE_OF\x10\x0f\x12\x12\n\x0ePROP_LESS_THAN\x10\x10\x12\x1b\n\x17PROP_LESS_THAN_OR_EQUAL\x10\x11\x12\x15\n\x11PROP_GREATER_THAN\x10\x12\x12\x1e\n\x1aPROP_GREATER_THAN_OR_EQUAL\x10\x13\x12\x0f\n\x0bPROP_BEFORE\x10\x14\x12\x0e\n\nPROP_AFTER\x10\x15\x12\x10\n\x0cPROP_MATCHES\x10\x16\x12\x17\n\x13PROP_DOES_NOT_MATCH\x10\x17\x12\x19\n\x15PROP_SEMVER_LESS_THAN\x10\x18\x12\x15\n\x11PROP_SEMVER_EQUAL\x10\x19\x12\x1c\n\x18PROP_SEMVER_GREATER_THAN\x10\x1a\"\xbb\x01\n\x07Loggers\x12(\n\x07loggers\x18\x01 \x03(\x0b\x32\x0e.prefab.LoggerR\x07loggers\x12\x19\n\x08start_at\x18\x02 \x01(\x03R\x07startAt\x12\x15\n\x06\x65nd_at\x18\x03 \x01(\x03R\x05\x65ndAt\x12#\n\rinstance_hash\x18\x04 \x01(\tR\x0cinstanceHash\x12!\n\tnamespace\x18\x05 \x01(\tH\x00R\tnamespace\x88\x01\x01\x42\x0c\n\n_namespace\"\x93\x02\n\x06Logger\x12\x1f\n\x0blogger_name\x18\x01 \x01(\tR\nloggerName\x12\x1b\n\x06traces\x18\x02 \x01(\x03H\x00R\x06traces\x88\x01\x01\x12\x1b\n\x06\x64\x65\x62ugs\x18\x03 \x01(\x03H\x01R\x06\x64\x65\x62ugs\x88\x01\x01\x12\x19\n\x05infos\x18\x04 \x01(\x03H\x02R\x05infos\x88\x01\x01\x12\x19\n\x05warns\x18\x05 \x01(\x03H\x03R\x05warns\x88\x01\x01\x12\x1b\n\x06\x65rrors\x18\x06 \x01(\x03H\x04R\x06\x65rrors\x88\x01\x01\x12\x1b\n\x06\x66\x61tals\x18\x07 \x01(\x03H\x05R\x06\x66\x61tals\x88\x01\x01\x42\t\n\x07_tracesB\t\n\x07_debugsB\x08\n\x06_infosB\x08\n\x06_warnsB\t\n\x07_errorsB\t\n\x07_fatals\"\x16\n\x14LoggerReportResponse\"\xd5\x04\n\rLimitResponse\x12\x16\n\x06passed\x18\x01 \x01(\x08R\x06passed\x12\x1d\n\nexpires_at\x18\x02 \x01(\x03R\texpiresAt\x12%\n\x0e\x65nforced_group\x18\x03 \x01(\tR\renforcedGroup\x12%\n\x0e\x63urrent_bucket\x18\x04 \x01(\x03R\rcurrentBucket\x12!\n\x0cpolicy_group\x18\x05 \x01(\tR\x0bpolicyGroup\x12G\n\x0bpolicy_name\x18\x06 \x01(\x0e\x32&.prefab.LimitResponse.LimitPolicyNamesR\npolicyName\x12!\n\x0cpolicy_limit\x18\x07 \x01(\x05R\x0bpolicyLimit\x12\x16\n\x06\x61mount\x18\x08 \x01(\x03R\x06\x61mount\x12$\n\x0elimit_reset_at\x18\t \x01(\x03R\x0climitResetAt\x12\x46\n\x0csafety_level\x18\n \x01(\x0e\x32#.prefab.LimitDefinition.SafetyLevelR\x0bsafetyLevel\"\xa9\x01\n\x10LimitPolicyNames\x12\x0b\n\x07NOT_SET\x10\x00\x12\x14\n\x10SECONDLY_ROLLING\x10\x01\x12\x14\n\x10MINUTELY_ROLLING\x10\x03\x12\x12\n\x0eHOURLY_ROLLING\x10\x05\x12\x11\n\rDAILY_ROLLING\x10\x07\x12\x13\n\x0fMONTHLY_ROLLING\x10\x08\x12\x0c\n\x08INFINITE\x10\t\x12\x12\n\x0eYEARLY_ROLLING\x10\n\"\xed\x02\n\x0cLimitRequest\x12\x1d\n\naccount_id\x18\x01 \x01(\x03R\taccountId\x12%\n\x0e\x61\x63quire_amount\x18\x02 \x01(\x05R\racquireAmount\x12\x16\n\x06groups\x18\x03 \x03(\tR\x06groups\x12I\n\x0elimit_combiner\x18\x04 \x01(\x0e\x32\".prefab.LimitRequest.LimitCombinerR\rlimitCombiner\x12\x34\n\x16\x61llow_partial_response\x18\x05 \x01(\x08R\x14\x61llowPartialResponse\x12\x46\n\x0csafety_level\x18\x06 \x01(\x0e\x32#.prefab.LimitDefinition.SafetyLevelR\x0bsafetyLevel\"6\n\rLimitCombiner\x12\x0b\n\x07NOT_SET\x10\x00\x12\x0b\n\x07MINIMUM\x10\x01\x12\x0b\n\x07MAXIMUM\x10\x02\"9\n\nContextSet\x12+\n\x08\x63ontexts\x18\x01 \x03(\x0b\x32\x0f.prefab.ContextR\x08\x63ontexts\"\xb0\x01\n\x07\x43ontext\x12\x17\n\x04type\x18\x01 \x01(\tH\x00R\x04type\x88\x01\x01\x12\x33\n\x06values\x18\x02 \x03(\x0b\x32\x1b.prefab.Context.ValuesEntryR\x06values\x1aN\n\x0bValuesEntry\x12\x10\n\x03key\x18\x01 \x01(\tR\x03key\x12)\n\x05value\x18\x02 \x01(\x0b\x32\x13.prefab.ConfigValueR\x05value:\x02\x38\x01\x42\x07\n\x05_type\"\xb3\x01\n\x08Identity\x12\x1b\n\x06lookup\x18\x01 \x01(\tH\x00R\x06lookup\x88\x01\x01\x12@\n\nattributes\x18\x02 \x03(\x0b\x32 .prefab.Identity.AttributesEntryR\nattributes\x1a=\n\x0f\x41ttributesEntry\x12\x10\n\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n\x05value\x18\x02 \x01(\tR\x05value:\x02\x38\x01\x42\t\n\x07_lookup\"\xa6\x03\n\x18\x43onfigEvaluationMetaData\x12-\n\x10\x63onfig_row_index\x18\x01 \x01(\x03H\x00R\x0e\x63onfigRowIndex\x88\x01\x01\x12;\n\x17\x63onditional_value_index\x18\x02 \x01(\x03H\x01R\x15\x63onditionalValueIndex\x88\x01\x01\x12\x35\n\x14weighted_value_index\x18\x03 \x01(\x03H\x02R\x12weightedValueIndex\x88\x01\x01\x12+\n\x04type\x18\x04 \x01(\x0e\x32\x12.prefab.ConfigTypeH\x03R\x04type\x88\x01\x01\x12\x13\n\x02id\x18\x05 \x01(\x03H\x04R\x02id\x88\x01\x01\x12<\n\nvalue_type\x18\x06 \x01(\x0e\x32\x18.prefab.Config.ValueTypeH\x05R\tvalueType\x88\x01\x01\x42\x13\n\x11_config_row_indexB\x1a\n\x18_conditional_value_indexB\x17\n\x15_weighted_value_indexB\x07\n\x05_typeB\x05\n\x03_idB\r\n\x0b_value_type\"\xf0\x03\n\x11\x43lientConfigValue\x12\x12\n\x03int\x18\x01 \x01(\x03H\x00R\x03int\x12\x18\n\x06string\x18\x02 \x01(\tH\x00R\x06string\x12\x18\n\x06\x64ouble\x18\x03 \x01(\x01H\x00R\x06\x64ouble\x12\x14\n\x04\x62ool\x18\x04 \x01(\x08H\x00R\x04\x62ool\x12/\n\tlog_level\x18\x05 \x01(\x0e\x32\x10.prefab.LogLevelH\x00R\x08logLevel\x12\x35\n\x0bstring_list\x18\x07 \x01(\x0b\x32\x12.prefab.StringListH\x00R\nstringList\x12/\n\tint_range\x18\x08 \x01(\x0b\x32\x10.prefab.IntRangeH\x00R\x08intRange\x12\x34\n\x08\x64uration\x18\t \x01(\x0b\x32\x16.prefab.ClientDurationH\x00R\x08\x64uration\x12\"\n\x04json\x18\n \x01(\x0b\x32\x0c.prefab.JsonH\x00R\x04json\x12\x63\n\x1a\x63onfig_evaluation_metadata\x18\x06 \x01(\x0b\x32 .prefab.ConfigEvaluationMetaDataH\x01R\x18\x63onfigEvaluationMetadata\x88\x01\x01\x42\x06\n\x04typeB\x1d\n\x1b_config_evaluation_metadata\"`\n\x0e\x43lientDuration\x12\x18\n\x07seconds\x18\x01 \x01(\x03R\x07seconds\x12\x14\n\x05nanos\x18\x02 \x01(\x05R\x05nanos\x12\x1e\n\ndefinition\x18\x03 \x01(\tR\ndefinition\"\xd8\x02\n\x11\x43onfigEvaluations\x12=\n\x06values\x18\x01 \x03(\x0b\x32%.prefab.ConfigEvaluations.ValuesEntryR\x06values\x12\x44\n\x0f\x61pikey_metadata\x18\x02 \x01(\x0b\x32\x16.prefab.ApiKeyMetadataH\x00R\x0e\x61pikeyMetadata\x88\x01\x01\x12@\n\x0f\x64\x65\x66\x61ult_context\x18\x03 \x01(\x0b\x32\x12.prefab.ContextSetH\x01R\x0e\x64\x65\x66\x61ultContext\x88\x01\x01\x1aT\n\x0bValuesEntry\x12\x10\n\x03key\x18\x01 \x01(\tR\x03key\x12/\n\x05value\x18\x02 \x01(\x0b\x32\x19.prefab.ClientConfigValueR\x05value:\x02\x38\x01\x42\x12\n\x10_apikey_metadataB\x12\n\x10_default_context\"\xf4\x02\n\x0fLimitDefinition\x12G\n\x0bpolicy_name\x18\x02 \x01(\x0e\x32&.prefab.LimitResponse.LimitPolicyNamesR\npolicyName\x12\x14\n\x05limit\x18\x03 \x01(\x05R\x05limit\x12\x14\n\x05\x62urst\x18\x04 \x01(\x05R\x05\x62urst\x12\x1d\n\naccount_id\x18\x05 \x01(\x03R\taccountId\x12#\n\rlast_modified\x18\x06 \x01(\x03R\x0clastModified\x12\x1e\n\nreturnable\x18\x07 \x01(\x08R\nreturnable\x12\x46\n\x0csafety_level\x18\x08 \x01(\x0e\x32#.prefab.LimitDefinition.SafetyLevelR\x0bsafetyLevel\"@\n\x0bSafetyLevel\x12\x0b\n\x07NOT_SET\x10\x00\x12\x12\n\x0eL4_BEST_EFFORT\x10\x04\x12\x10\n\x0cL5_BOMBPROOF\x10\x05\"M\n\x10LimitDefinitions\x12\x39\n\x0b\x64\x65\x66initions\x18\x01 \x03(\x0b\x32\x17.prefab.LimitDefinitionR\x0b\x64\x65\x66initions\"\xc8\x01\n\x0f\x42ufferedRequest\x12\x1d\n\naccount_id\x18\x01 \x01(\x03R\taccountId\x12\x16\n\x06method\x18\x02 \x01(\tR\x06method\x12\x10\n\x03uri\x18\x03 \x01(\tR\x03uri\x12\x12\n\x04\x62ody\x18\x04 \x01(\tR\x04\x62ody\x12!\n\x0climit_groups\x18\x05 \x03(\tR\x0blimitGroups\x12!\n\x0c\x63ontent_type\x18\x06 \x01(\tR\x0b\x63ontentType\x12\x12\n\x04\x66ifo\x18\x07 \x01(\x08R\x04\x66ifo\"\xde\x01\n\x0c\x42\x61tchRequest\x12\x1d\n\naccount_id\x18\x01 \x01(\x03R\taccountId\x12\x16\n\x06method\x18\x02 \x01(\tR\x06method\x12\x10\n\x03uri\x18\x03 \x01(\tR\x03uri\x12\x12\n\x04\x62ody\x18\x04 \x01(\tR\x04\x62ody\x12!\n\x0climit_groups\x18\x05 \x03(\tR\x0blimitGroups\x12%\n\x0e\x62\x61tch_template\x18\x06 \x01(\tR\rbatchTemplate\x12\'\n\x0f\x62\x61tch_separator\x18\x07 \x01(\tR\x0e\x62\x61tchSeparator\")\n\rBasicResponse\x12\x18\n\x07message\x18\x01 \x01(\tR\x07message\"C\n\x10\x43reationResponse\x12\x18\n\x07message\x18\x01 \x01(\tR\x07message\x12\x15\n\x06new_id\x18\x02 \x01(\x03R\x05newId\"\x9b\x01\n\x07IdBlock\x12\x1d\n\nproject_id\x18\x01 \x01(\x03R\tprojectId\x12$\n\x0eproject_env_id\x18\x02 \x01(\x03R\x0cprojectEnvId\x12#\n\rsequence_name\x18\x03 \x01(\tR\x0csequenceName\x12\x14\n\x05start\x18\x04 \x01(\x03R\x05start\x12\x10\n\x03\x65nd\x18\x05 \x01(\x03R\x03\x65nd\"\x8e\x01\n\x0eIdBlockRequest\x12\x1d\n\nproject_id\x18\x01 \x01(\x03R\tprojectId\x12$\n\x0eproject_env_id\x18\x02 \x01(\x03R\x0cprojectEnvId\x12#\n\rsequence_name\x18\x03 \x01(\tR\x0csequenceName\x12\x12\n\x04size\x18\x04 \x01(\x03R\x04size\"\xa8\x01\n\x0c\x43ontextShape\x12\x12\n\x04name\x18\x01 \x01(\tR\x04name\x12\x45\n\x0b\x66ield_types\x18\x02 \x03(\x0b\x32$.prefab.ContextShape.FieldTypesEntryR\nfieldTypes\x1a=\n\x0f\x46ieldTypesEntry\x12\x10\n\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n\x05value\x18\x02 \x01(\x05R\x05value:\x02\x38\x01\"n\n\rContextShapes\x12,\n\x06shapes\x18\x01 \x03(\x0b\x32\x14.prefab.ContextShapeR\x06shapes\x12!\n\tnamespace\x18\x02 \x01(\tH\x00R\tnamespace\x88\x01\x01\x42\x0c\n\n_namespace\"T\n\rEvaluatedKeys\x12\x12\n\x04keys\x18\x01 \x03(\tR\x04keys\x12!\n\tnamespace\x18\x02 \x01(\tH\x00R\tnamespace\x88\x01\x01\x42\x0c\n\n_namespace\"\xc3\x01\n\x0f\x45valuatedConfig\x12\x10\n\x03key\x18\x01 \x01(\tR\x03key\x12%\n\x0e\x63onfig_version\x18\x02 \x01(\x03R\rconfigVersion\x12+\n\x06result\x18\x03 \x01(\x0b\x32\x13.prefab.ConfigValueR\x06result\x12,\n\x07\x63ontext\x18\x04 \x01(\x0b\x32\x12.prefab.ContextSetR\x07\x63ontext\x12\x1c\n\ttimestamp\x18\x05 \x01(\x03R\ttimestamp\"E\n\x10\x45valuatedConfigs\x12\x31\n\x07\x63onfigs\x18\x01 \x03(\x0b\x32\x17.prefab.EvaluatedConfigR\x07\x63onfigs\"\xb6\x04\n\x17\x43onfigEvaluationCounter\x12\x14\n\x05\x63ount\x18\x01 \x01(\x03R\x05\x63ount\x12 \n\tconfig_id\x18\x02 \x01(\x03H\x00R\x08\x63onfigId\x88\x01\x01\x12*\n\x0eselected_index\x18\x03 \x01(\rH\x01R\rselectedIndex\x88\x01\x01\x12?\n\x0eselected_value\x18\x04 \x01(\x0b\x32\x13.prefab.ConfigValueH\x02R\rselectedValue\x88\x01\x01\x12-\n\x10\x63onfig_row_index\x18\x05 \x01(\rH\x03R\x0e\x63onfigRowIndex\x88\x01\x01\x12;\n\x17\x63onditional_value_index\x18\x06 \x01(\rH\x04R\x15\x63onditionalValueIndex\x88\x01\x01\x12\x35\n\x14weighted_value_index\x18\x07 \x01(\rH\x05R\x12weightedValueIndex\x88\x01\x01\x12>\n\x06reason\x18\x08 \x01(\x0e\x32&.prefab.ConfigEvaluationCounter.ReasonR\x06reason\"\x15\n\x06Reason\x12\x0b\n\x07UNKNOWN\x10\x00\x42\x0c\n\n_config_idB\x11\n\x0f_selected_indexB\x11\n\x0f_selected_valueB\x13\n\x11_config_row_indexB\x1a\n\x18_conditional_value_indexB\x17\n\x15_weighted_value_index\"\x90\x01\n\x17\x43onfigEvaluationSummary\x12\x10\n\x03key\x18\x01 \x01(\tR\x03key\x12&\n\x04type\x18\x02 \x01(\x0e\x32\x12.prefab.ConfigTypeR\x04type\x12;\n\x08\x63ounters\x18\x03 \x03(\x0b\x32\x1f.prefab.ConfigEvaluationCounterR\x08\x63ounters\"\x82\x01\n\x19\x43onfigEvaluationSummaries\x12\x14\n\x05start\x18\x01 \x01(\x03R\x05start\x12\x10\n\x03\x65nd\x18\x02 \x01(\x03R\x03\x65nd\x12=\n\tsummaries\x18\x03 \x03(\x0b\x32\x1f.prefab.ConfigEvaluationSummaryR\tsummaries\"s\n\x15LoggersTelemetryEvent\x12(\n\x07loggers\x18\x01 \x03(\x0b\x32\x0e.prefab.LoggerR\x07loggers\x12\x19\n\x08start_at\x18\x02 \x01(\x03R\x07startAt\x12\x15\n\x06\x65nd_at\x18\x03 \x01(\x03R\x05\x65ndAt\"\xd9\x02\n\x0eTelemetryEvent\x12\x41\n\tsummaries\x18\x02 \x01(\x0b\x32!.prefab.ConfigEvaluationSummariesH\x00R\tsummaries\x12\x44\n\x10\x65xample_contexts\x18\x03 \x01(\x0b\x32\x17.prefab.ExampleContextsH\x00R\x0f\x65xampleContexts\x12\x38\n\x0c\x63lient_stats\x18\x04 \x01(\x0b\x32\x13.prefab.ClientStatsH\x00R\x0b\x63lientStats\x12\x39\n\x07loggers\x18\x05 \x01(\x0b\x32\x1d.prefab.LoggersTelemetryEventH\x00R\x07loggers\x12>\n\x0e\x63ontext_shapes\x18\x06 \x01(\x0b\x32\x15.prefab.ContextShapesH\x00R\rcontextShapesB\t\n\x07payload\"f\n\x0fTelemetryEvents\x12#\n\rinstance_hash\x18\x01 \x01(\tR\x0cinstanceHash\x12.\n\x06\x65vents\x18\x02 \x03(\x0b\x32\x16.prefab.TelemetryEventR\x06\x65vents\"3\n\x17TelemetryEventsResponse\x12\x18\n\x07success\x18\x01 \x01(\x08R\x07success\"E\n\x0f\x45xampleContexts\x12\x32\n\x08\x65xamples\x18\x01 \x03(\x0b\x32\x16.prefab.ExampleContextR\x08\x65xamples\"b\n\x0e\x45xampleContext\x12\x1c\n\ttimestamp\x18\x01 \x01(\x03R\ttimestamp\x12\x32\n\ncontextSet\x18\x02 \x01(\x0b\x32\x12.prefab.ContextSetR\ncontextSet\"e\n\x0b\x43lientStats\x12\x14\n\x05start\x18\x01 \x01(\x03R\x05start\x12\x10\n\x03\x65nd\x18\x02 \x01(\x03R\x03\x65nd\x12.\n\x13\x64ropped_event_count\x18\x03 \x01(\x04R\x11\x64roppedEventCount\"\x91\x01\n\x06Schema\x12\x16\n\x06schema\x18\x01 \x01(\tR\x06schema\x12:\n\x0bschema_type\x18\x02 \x01(\x0e\x32\x19.prefab.Schema.SchemaTypeR\nschemaType\"3\n\nSchemaType\x12\x0b\n\x07UNKNOWN\x10\x00\x12\x07\n\x03ZOD\x10\x01\x12\x0f\n\x0bJSON_SCHEMA\x10\x02*:\n\x0eProvidedSource\x12\x1b\n\x17PROVIDED_SOURCE_NOT_SET\x10\x00\x12\x0b\n\x07\x45NV_VAR\x10\x01*\x8e\x01\n\nConfigType\x12\x17\n\x13NOT_SET_CONFIG_TYPE\x10\x00\x12\n\n\x06\x43ONFIG\x10\x01\x12\x10\n\x0c\x46\x45\x41TURE_FLAG\x10\x02\x12\r\n\tLOG_LEVEL\x10\x03\x12\x0b\n\x07SEGMENT\x10\x04\x12\x14\n\x10LIMIT_DEFINITION\x10\x05\x12\x0b\n\x07\x44\x45LETED\x10\x06\x12\n\n\x06SCHEMA\x10\x07*a\n\x08LogLevel\x12\x15\n\x11NOT_SET_LOG_LEVEL\x10\x00\x12\t\n\x05TRACE\x10\x01\x12\t\n\x05\x44\x45\x42UG\x10\x02\x12\x08\n\x04INFO\x10\x03\x12\x08\n\x04WARN\x10\x05\x12\t\n\x05\x45RROR\x10\x06\x12\t\n\x05\x46\x41TAL\x10\t*G\n\tOnFailure\x12\x0b\n\x07NOT_SET\x10\x00\x12\x10\n\x0cLOG_AND_PASS\x10\x01\x12\x10\n\x0cLOG_AND_FAIL\x10\x02\x12\t\n\x05THROW\x10\x03\x42L\n\x13\x63loud.prefab.domainB\x06PrefabZ-github.com/prefab-cloud/prefab-cloud-go/protob\x06proto3') +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x0cprefab.proto\x12\x06prefab\"{\n\x14\x43onfigServicePointer\x12\x1d\n\nproject_id\x18\x01 \x01(\x03R\tprojectId\x12\x1e\n\x0bstart_at_id\x18\x02 \x01(\x03R\tstartAtId\x12$\n\x0eproject_env_id\x18\x03 \x01(\x03R\x0cprojectEnvId\"\xaa\x06\n\x0b\x43onfigValue\x12\x12\n\x03int\x18\x01 \x01(\x03H\x00R\x03int\x12\x18\n\x06string\x18\x02 \x01(\tH\x00R\x06string\x12\x16\n\x05\x62ytes\x18\x03 \x01(\x0cH\x00R\x05\x62ytes\x12\x18\n\x06\x64ouble\x18\x04 \x01(\x01H\x00R\x06\x64ouble\x12\x14\n\x04\x62ool\x18\x05 \x01(\x08H\x00R\x04\x62ool\x12\x41\n\x0fweighted_values\x18\x06 \x01(\x0b\x32\x16.prefab.WeightedValuesH\x00R\x0eweightedValues\x12\x44\n\x10limit_definition\x18\x07 \x01(\x0b\x32\x17.prefab.LimitDefinitionH\x00R\x0flimitDefinition\x12/\n\tlog_level\x18\t \x01(\x0e\x32\x10.prefab.LogLevelH\x00R\x08logLevel\x12\x35\n\x0bstring_list\x18\n \x01(\x0b\x32\x12.prefab.StringListH\x00R\nstringList\x12/\n\tint_range\x18\x0b \x01(\x0b\x32\x10.prefab.IntRangeH\x00R\x08intRange\x12.\n\x08provided\x18\x0c \x01(\x0b\x32\x10.prefab.ProvidedH\x00R\x08provided\x12\x31\n\x08\x64uration\x18\x0f \x01(\x0b\x32\x13.prefab.IsoDurationH\x00R\x08\x64uration\x12\"\n\x04json\x18\x10 \x01(\x0b\x32\x0c.prefab.JsonH\x00R\x04json\x12(\n\x06schema\x18\x11 \x01(\x0b\x32\x0e.prefab.SchemaH\x00R\x06schema\x12\'\n\x0c\x63onfidential\x18\r \x01(\x08H\x01R\x0c\x63onfidential\x88\x01\x01\x12&\n\x0c\x64\x65\x63rypt_with\x18\x0e \x01(\tH\x02R\x0b\x64\x65\x63ryptWith\x88\x01\x01\x12\x17\n\x04name\x18\x12 \x01(\tH\x03R\x04name\x88\x01\x01\x12%\n\x0b\x64\x65scription\x18\x13 \x01(\tH\x04R\x0b\x64\x65scription\x88\x01\x01\x42\x06\n\x04typeB\x0f\n\r_confidentialB\x0f\n\r_decrypt_withB\x07\n\x05_nameB\x0e\n\x0c_description\"\x1a\n\x04Json\x12\x12\n\x04json\x18\x01 \x01(\tR\x04json\"-\n\x0bIsoDuration\x12\x1e\n\ndefinition\x18\x01 \x01(\tR\ndefinition\"r\n\x08Provided\x12\x33\n\x06source\x18\x01 \x01(\x0e\x32\x16.prefab.ProvidedSourceH\x00R\x06source\x88\x01\x01\x12\x1b\n\x06lookup\x18\x02 \x01(\tH\x01R\x06lookup\x88\x01\x01\x42\t\n\x07_sourceB\t\n\x07_lookup\"N\n\x08IntRange\x12\x19\n\x05start\x18\x01 \x01(\x03H\x00R\x05start\x88\x01\x01\x12\x15\n\x03\x65nd\x18\x02 \x01(\x03H\x01R\x03\x65nd\x88\x01\x01\x42\x08\n\x06_startB\x06\n\x04_end\"$\n\nStringList\x12\x16\n\x06values\x18\x01 \x03(\tR\x06values\"R\n\rWeightedValue\x12\x16\n\x06weight\x18\x01 \x01(\x05R\x06weight\x12)\n\x05value\x18\x02 \x01(\x0b\x32\x13.prefab.ConfigValueR\x05value\"\xa2\x01\n\x0eWeightedValues\x12>\n\x0fweighted_values\x18\x01 \x03(\x0b\x32\x15.prefab.WeightedValueR\x0eweightedValues\x12\x36\n\x15hash_by_property_name\x18\x02 \x01(\tH\x00R\x12hashByPropertyName\x88\x01\x01\x42\x18\n\x16_hash_by_property_name\"g\n\x0e\x41piKeyMetadata\x12\x1a\n\x06key_id\x18\x01 \x01(\tH\x00R\x05keyId\x88\x01\x01\x12\x1c\n\x07user_id\x18\x03 \x01(\tH\x01R\x06userId\x88\x01\x01\x42\t\n\x07_key_idB\n\n\x08_user_idJ\x04\x08\x02\x10\x03\"\xea\x02\n\x07\x43onfigs\x12(\n\x07\x63onfigs\x18\x01 \x03(\x0b\x32\x0e.prefab.ConfigR\x07\x63onfigs\x12R\n\x16\x63onfig_service_pointer\x18\x02 \x01(\x0b\x32\x1c.prefab.ConfigServicePointerR\x14\x63onfigServicePointer\x12\x44\n\x0f\x61pikey_metadata\x18\x03 \x01(\x0b\x32\x16.prefab.ApiKeyMetadataH\x00R\x0e\x61pikeyMetadata\x88\x01\x01\x12@\n\x0f\x64\x65\x66\x61ult_context\x18\x04 \x01(\x0b\x32\x12.prefab.ContextSetH\x01R\x0e\x64\x65\x66\x61ultContext\x88\x01\x01\x12\"\n\nkeep_alive\x18\x05 \x01(\x08H\x02R\tkeepAlive\x88\x01\x01\x42\x12\n\x10_apikey_metadataB\x12\n\x10_default_contextB\r\n\x0b_keep_alive\"\x96\x05\n\x06\x43onfig\x12\x0e\n\x02id\x18\x01 \x01(\x03R\x02id\x12\x1d\n\nproject_id\x18\x02 \x01(\x03R\tprojectId\x12\x10\n\x03key\x18\x03 \x01(\tR\x03key\x12\x30\n\nchanged_by\x18\x04 \x01(\x0b\x32\x11.prefab.ChangedByR\tchangedBy\x12%\n\x04rows\x18\x05 \x03(\x0b\x32\x11.prefab.ConfigRowR\x04rows\x12>\n\x10\x61llowable_values\x18\x06 \x03(\x0b\x32\x13.prefab.ConfigValueR\x0f\x61llowableValues\x12\x33\n\x0b\x63onfig_type\x18\x07 \x01(\x0e\x32\x12.prefab.ConfigTypeR\nconfigType\x12\x1e\n\x08\x64raft_id\x18\x08 \x01(\x03H\x00R\x07\x64raftId\x88\x01\x01\x12\x37\n\nvalue_type\x18\t \x01(\x0e\x32\x18.prefab.Config.ValueTypeR\tvalueType\x12+\n\x12send_to_client_sdk\x18\n \x01(\x08R\x0fsendToClientSdk\x12\"\n\nschema_key\x18\x0b \x01(\tH\x01R\tschemaKey\x88\x01\x01\"\xb6\x01\n\tValueType\x12\x16\n\x12NOT_SET_VALUE_TYPE\x10\x00\x12\x07\n\x03INT\x10\x01\x12\n\n\x06STRING\x10\x02\x12\t\n\x05\x42YTES\x10\x03\x12\n\n\x06\x44OUBLE\x10\x04\x12\x08\n\x04\x42OOL\x10\x05\x12\x14\n\x10LIMIT_DEFINITION\x10\x07\x12\r\n\tLOG_LEVEL\x10\t\x12\x0f\n\x0bSTRING_LIST\x10\n\x12\r\n\tINT_RANGE\x10\x0b\x12\x0c\n\x08\x44URATION\x10\x0c\x12\x08\n\x04JSON\x10\rB\x0b\n\t_draft_idB\r\n\x0b_schema_key\"}\n\tChangedBy\x12\x17\n\x07user_id\x18\x01 \x01(\x03R\x06userId\x12\x14\n\x05\x65mail\x18\x02 \x01(\tR\x05\x65mail\x12\x1c\n\napi_key_id\x18\x03 \x01(\tR\x08\x61piKeyId\x12#\n\ruser_identity\x18\x04 \x01(\tR\x0cuserIdentity\"\x92\x02\n\tConfigRow\x12)\n\x0eproject_env_id\x18\x01 \x01(\x03H\x00R\x0cprojectEnvId\x88\x01\x01\x12\x30\n\x06values\x18\x02 \x03(\x0b\x32\x18.prefab.ConditionalValueR\x06values\x12\x41\n\nproperties\x18\x03 \x03(\x0b\x32!.prefab.ConfigRow.PropertiesEntryR\nproperties\x1aR\n\x0fPropertiesEntry\x12\x10\n\x03key\x18\x01 \x01(\tR\x03key\x12)\n\x05value\x18\x02 \x01(\x0b\x32\x13.prefab.ConfigValueR\x05value:\x02\x38\x01\x42\x11\n\x0f_project_env_id\"l\n\x10\x43onditionalValue\x12-\n\x08\x63riteria\x18\x01 \x03(\x0b\x32\x11.prefab.CriterionR\x08\x63riteria\x12)\n\x05value\x18\x02 \x01(\x0b\x32\x13.prefab.ConfigValueR\x05value\"\xbc\x06\n\tCriterion\x12#\n\rproperty_name\x18\x01 \x01(\tR\x0cpropertyName\x12?\n\x08operator\x18\x02 \x01(\x0e\x32#.prefab.Criterion.CriterionOperatorR\x08operator\x12\x39\n\x0evalue_to_match\x18\x03 \x01(\x0b\x32\x13.prefab.ConfigValueR\x0cvalueToMatch\"\x8d\x05\n\x11\x43riterionOperator\x12\x0b\n\x07NOT_SET\x10\x00\x12\x11\n\rLOOKUP_KEY_IN\x10\x01\x12\x15\n\x11LOOKUP_KEY_NOT_IN\x10\x02\x12\n\n\x06IN_SEG\x10\x03\x12\x0e\n\nNOT_IN_SEG\x10\x04\x12\x0f\n\x0b\x41LWAYS_TRUE\x10\x05\x12\x12\n\x0ePROP_IS_ONE_OF\x10\x06\x12\x16\n\x12PROP_IS_NOT_ONE_OF\x10\x07\x12\x19\n\x15PROP_ENDS_WITH_ONE_OF\x10\x08\x12!\n\x1dPROP_DOES_NOT_END_WITH_ONE_OF\x10\t\x12\x16\n\x12HIERARCHICAL_MATCH\x10\n\x12\x10\n\x0cIN_INT_RANGE\x10\x0b\x12\x1b\n\x17PROP_STARTS_WITH_ONE_OF\x10\x0c\x12#\n\x1fPROP_DOES_NOT_START_WITH_ONE_OF\x10\r\x12\x18\n\x14PROP_CONTAINS_ONE_OF\x10\x0e\x12 \n\x1cPROP_DOES_NOT_CONTAIN_ONE_OF\x10\x0f\x12\x12\n\x0ePROP_LESS_THAN\x10\x10\x12\x1b\n\x17PROP_LESS_THAN_OR_EQUAL\x10\x11\x12\x15\n\x11PROP_GREATER_THAN\x10\x12\x12\x1e\n\x1aPROP_GREATER_THAN_OR_EQUAL\x10\x13\x12\x0f\n\x0bPROP_BEFORE\x10\x14\x12\x0e\n\nPROP_AFTER\x10\x15\x12\x10\n\x0cPROP_MATCHES\x10\x16\x12\x17\n\x13PROP_DOES_NOT_MATCH\x10\x17\x12\x19\n\x15PROP_SEMVER_LESS_THAN\x10\x18\x12\x15\n\x11PROP_SEMVER_EQUAL\x10\x19\x12\x1c\n\x18PROP_SEMVER_GREATER_THAN\x10\x1a\"\xbb\x01\n\x07Loggers\x12(\n\x07loggers\x18\x01 \x03(\x0b\x32\x0e.prefab.LoggerR\x07loggers\x12\x19\n\x08start_at\x18\x02 \x01(\x03R\x07startAt\x12\x15\n\x06\x65nd_at\x18\x03 \x01(\x03R\x05\x65ndAt\x12#\n\rinstance_hash\x18\x04 \x01(\tR\x0cinstanceHash\x12!\n\tnamespace\x18\x05 \x01(\tH\x00R\tnamespace\x88\x01\x01\x42\x0c\n\n_namespace\"\x93\x02\n\x06Logger\x12\x1f\n\x0blogger_name\x18\x01 \x01(\tR\nloggerName\x12\x1b\n\x06traces\x18\x02 \x01(\x03H\x00R\x06traces\x88\x01\x01\x12\x1b\n\x06\x64\x65\x62ugs\x18\x03 \x01(\x03H\x01R\x06\x64\x65\x62ugs\x88\x01\x01\x12\x19\n\x05infos\x18\x04 \x01(\x03H\x02R\x05infos\x88\x01\x01\x12\x19\n\x05warns\x18\x05 \x01(\x03H\x03R\x05warns\x88\x01\x01\x12\x1b\n\x06\x65rrors\x18\x06 \x01(\x03H\x04R\x06\x65rrors\x88\x01\x01\x12\x1b\n\x06\x66\x61tals\x18\x07 \x01(\x03H\x05R\x06\x66\x61tals\x88\x01\x01\x42\t\n\x07_tracesB\t\n\x07_debugsB\x08\n\x06_infosB\x08\n\x06_warnsB\t\n\x07_errorsB\t\n\x07_fatals\"\x16\n\x14LoggerReportResponse\"\xd5\x04\n\rLimitResponse\x12\x16\n\x06passed\x18\x01 \x01(\x08R\x06passed\x12\x1d\n\nexpires_at\x18\x02 \x01(\x03R\texpiresAt\x12%\n\x0e\x65nforced_group\x18\x03 \x01(\tR\renforcedGroup\x12%\n\x0e\x63urrent_bucket\x18\x04 \x01(\x03R\rcurrentBucket\x12!\n\x0cpolicy_group\x18\x05 \x01(\tR\x0bpolicyGroup\x12G\n\x0bpolicy_name\x18\x06 \x01(\x0e\x32&.prefab.LimitResponse.LimitPolicyNamesR\npolicyName\x12!\n\x0cpolicy_limit\x18\x07 \x01(\x05R\x0bpolicyLimit\x12\x16\n\x06\x61mount\x18\x08 \x01(\x03R\x06\x61mount\x12$\n\x0elimit_reset_at\x18\t \x01(\x03R\x0climitResetAt\x12\x46\n\x0csafety_level\x18\n \x01(\x0e\x32#.prefab.LimitDefinition.SafetyLevelR\x0bsafetyLevel\"\xa9\x01\n\x10LimitPolicyNames\x12\x0b\n\x07NOT_SET\x10\x00\x12\x14\n\x10SECONDLY_ROLLING\x10\x01\x12\x14\n\x10MINUTELY_ROLLING\x10\x03\x12\x12\n\x0eHOURLY_ROLLING\x10\x05\x12\x11\n\rDAILY_ROLLING\x10\x07\x12\x13\n\x0fMONTHLY_ROLLING\x10\x08\x12\x0c\n\x08INFINITE\x10\t\x12\x12\n\x0eYEARLY_ROLLING\x10\n\"\xed\x02\n\x0cLimitRequest\x12\x1d\n\naccount_id\x18\x01 \x01(\x03R\taccountId\x12%\n\x0e\x61\x63quire_amount\x18\x02 \x01(\x05R\racquireAmount\x12\x16\n\x06groups\x18\x03 \x03(\tR\x06groups\x12I\n\x0elimit_combiner\x18\x04 \x01(\x0e\x32\".prefab.LimitRequest.LimitCombinerR\rlimitCombiner\x12\x34\n\x16\x61llow_partial_response\x18\x05 \x01(\x08R\x14\x61llowPartialResponse\x12\x46\n\x0csafety_level\x18\x06 \x01(\x0e\x32#.prefab.LimitDefinition.SafetyLevelR\x0bsafetyLevel\"6\n\rLimitCombiner\x12\x0b\n\x07NOT_SET\x10\x00\x12\x0b\n\x07MINIMUM\x10\x01\x12\x0b\n\x07MAXIMUM\x10\x02\"9\n\nContextSet\x12+\n\x08\x63ontexts\x18\x01 \x03(\x0b\x32\x0f.prefab.ContextR\x08\x63ontexts\"\xb0\x01\n\x07\x43ontext\x12\x17\n\x04type\x18\x01 \x01(\tH\x00R\x04type\x88\x01\x01\x12\x33\n\x06values\x18\x02 \x03(\x0b\x32\x1b.prefab.Context.ValuesEntryR\x06values\x1aN\n\x0bValuesEntry\x12\x10\n\x03key\x18\x01 \x01(\tR\x03key\x12)\n\x05value\x18\x02 \x01(\x0b\x32\x13.prefab.ConfigValueR\x05value:\x02\x38\x01\x42\x07\n\x05_type\"\xb3\x01\n\x08Identity\x12\x1b\n\x06lookup\x18\x01 \x01(\tH\x00R\x06lookup\x88\x01\x01\x12@\n\nattributes\x18\x02 \x03(\x0b\x32 .prefab.Identity.AttributesEntryR\nattributes\x1a=\n\x0f\x41ttributesEntry\x12\x10\n\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n\x05value\x18\x02 \x01(\tR\x05value:\x02\x38\x01\x42\t\n\x07_lookup\"\xa6\x03\n\x18\x43onfigEvaluationMetaData\x12-\n\x10\x63onfig_row_index\x18\x01 \x01(\x03H\x00R\x0e\x63onfigRowIndex\x88\x01\x01\x12;\n\x17\x63onditional_value_index\x18\x02 \x01(\x03H\x01R\x15\x63onditionalValueIndex\x88\x01\x01\x12\x35\n\x14weighted_value_index\x18\x03 \x01(\x03H\x02R\x12weightedValueIndex\x88\x01\x01\x12+\n\x04type\x18\x04 \x01(\x0e\x32\x12.prefab.ConfigTypeH\x03R\x04type\x88\x01\x01\x12\x13\n\x02id\x18\x05 \x01(\x03H\x04R\x02id\x88\x01\x01\x12<\n\nvalue_type\x18\x06 \x01(\x0e\x32\x18.prefab.Config.ValueTypeH\x05R\tvalueType\x88\x01\x01\x42\x13\n\x11_config_row_indexB\x1a\n\x18_conditional_value_indexB\x17\n\x15_weighted_value_indexB\x07\n\x05_typeB\x05\n\x03_idB\r\n\x0b_value_type\"\xf0\x03\n\x11\x43lientConfigValue\x12\x12\n\x03int\x18\x01 \x01(\x03H\x00R\x03int\x12\x18\n\x06string\x18\x02 \x01(\tH\x00R\x06string\x12\x18\n\x06\x64ouble\x18\x03 \x01(\x01H\x00R\x06\x64ouble\x12\x14\n\x04\x62ool\x18\x04 \x01(\x08H\x00R\x04\x62ool\x12/\n\tlog_level\x18\x05 \x01(\x0e\x32\x10.prefab.LogLevelH\x00R\x08logLevel\x12\x35\n\x0bstring_list\x18\x07 \x01(\x0b\x32\x12.prefab.StringListH\x00R\nstringList\x12/\n\tint_range\x18\x08 \x01(\x0b\x32\x10.prefab.IntRangeH\x00R\x08intRange\x12\x34\n\x08\x64uration\x18\t \x01(\x0b\x32\x16.prefab.ClientDurationH\x00R\x08\x64uration\x12\"\n\x04json\x18\n \x01(\x0b\x32\x0c.prefab.JsonH\x00R\x04json\x12\x63\n\x1a\x63onfig_evaluation_metadata\x18\x06 \x01(\x0b\x32 .prefab.ConfigEvaluationMetaDataH\x01R\x18\x63onfigEvaluationMetadata\x88\x01\x01\x42\x06\n\x04typeB\x1d\n\x1b_config_evaluation_metadata\"`\n\x0e\x43lientDuration\x12\x18\n\x07seconds\x18\x01 \x01(\x03R\x07seconds\x12\x14\n\x05nanos\x18\x02 \x01(\x05R\x05nanos\x12\x1e\n\ndefinition\x18\x03 \x01(\tR\ndefinition\"\xd8\x02\n\x11\x43onfigEvaluations\x12=\n\x06values\x18\x01 \x03(\x0b\x32%.prefab.ConfigEvaluations.ValuesEntryR\x06values\x12\x44\n\x0f\x61pikey_metadata\x18\x02 \x01(\x0b\x32\x16.prefab.ApiKeyMetadataH\x00R\x0e\x61pikeyMetadata\x88\x01\x01\x12@\n\x0f\x64\x65\x66\x61ult_context\x18\x03 \x01(\x0b\x32\x12.prefab.ContextSetH\x01R\x0e\x64\x65\x66\x61ultContext\x88\x01\x01\x1aT\n\x0bValuesEntry\x12\x10\n\x03key\x18\x01 \x01(\tR\x03key\x12/\n\x05value\x18\x02 \x01(\x0b\x32\x19.prefab.ClientConfigValueR\x05value:\x02\x38\x01\x42\x12\n\x10_apikey_metadataB\x12\n\x10_default_context\"\xf4\x02\n\x0fLimitDefinition\x12G\n\x0bpolicy_name\x18\x02 \x01(\x0e\x32&.prefab.LimitResponse.LimitPolicyNamesR\npolicyName\x12\x14\n\x05limit\x18\x03 \x01(\x05R\x05limit\x12\x14\n\x05\x62urst\x18\x04 \x01(\x05R\x05\x62urst\x12\x1d\n\naccount_id\x18\x05 \x01(\x03R\taccountId\x12#\n\rlast_modified\x18\x06 \x01(\x03R\x0clastModified\x12\x1e\n\nreturnable\x18\x07 \x01(\x08R\nreturnable\x12\x46\n\x0csafety_level\x18\x08 \x01(\x0e\x32#.prefab.LimitDefinition.SafetyLevelR\x0bsafetyLevel\"@\n\x0bSafetyLevel\x12\x0b\n\x07NOT_SET\x10\x00\x12\x12\n\x0eL4_BEST_EFFORT\x10\x04\x12\x10\n\x0cL5_BOMBPROOF\x10\x05\"M\n\x10LimitDefinitions\x12\x39\n\x0b\x64\x65\x66initions\x18\x01 \x03(\x0b\x32\x17.prefab.LimitDefinitionR\x0b\x64\x65\x66initions\"\xc8\x01\n\x0f\x42ufferedRequest\x12\x1d\n\naccount_id\x18\x01 \x01(\x03R\taccountId\x12\x16\n\x06method\x18\x02 \x01(\tR\x06method\x12\x10\n\x03uri\x18\x03 \x01(\tR\x03uri\x12\x12\n\x04\x62ody\x18\x04 \x01(\tR\x04\x62ody\x12!\n\x0climit_groups\x18\x05 \x03(\tR\x0blimitGroups\x12!\n\x0c\x63ontent_type\x18\x06 \x01(\tR\x0b\x63ontentType\x12\x12\n\x04\x66ifo\x18\x07 \x01(\x08R\x04\x66ifo\"\xde\x01\n\x0c\x42\x61tchRequest\x12\x1d\n\naccount_id\x18\x01 \x01(\x03R\taccountId\x12\x16\n\x06method\x18\x02 \x01(\tR\x06method\x12\x10\n\x03uri\x18\x03 \x01(\tR\x03uri\x12\x12\n\x04\x62ody\x18\x04 \x01(\tR\x04\x62ody\x12!\n\x0climit_groups\x18\x05 \x03(\tR\x0blimitGroups\x12%\n\x0e\x62\x61tch_template\x18\x06 \x01(\tR\rbatchTemplate\x12\'\n\x0f\x62\x61tch_separator\x18\x07 \x01(\tR\x0e\x62\x61tchSeparator\")\n\rBasicResponse\x12\x18\n\x07message\x18\x01 \x01(\tR\x07message\"C\n\x10\x43reationResponse\x12\x18\n\x07message\x18\x01 \x01(\tR\x07message\x12\x15\n\x06new_id\x18\x02 \x01(\x03R\x05newId\"\x9b\x01\n\x07IdBlock\x12\x1d\n\nproject_id\x18\x01 \x01(\x03R\tprojectId\x12$\n\x0eproject_env_id\x18\x02 \x01(\x03R\x0cprojectEnvId\x12#\n\rsequence_name\x18\x03 \x01(\tR\x0csequenceName\x12\x14\n\x05start\x18\x04 \x01(\x03R\x05start\x12\x10\n\x03\x65nd\x18\x05 \x01(\x03R\x03\x65nd\"\x8e\x01\n\x0eIdBlockRequest\x12\x1d\n\nproject_id\x18\x01 \x01(\x03R\tprojectId\x12$\n\x0eproject_env_id\x18\x02 \x01(\x03R\x0cprojectEnvId\x12#\n\rsequence_name\x18\x03 \x01(\tR\x0csequenceName\x12\x12\n\x04size\x18\x04 \x01(\x03R\x04size\"\xa8\x01\n\x0c\x43ontextShape\x12\x12\n\x04name\x18\x01 \x01(\tR\x04name\x12\x45\n\x0b\x66ield_types\x18\x02 \x03(\x0b\x32$.prefab.ContextShape.FieldTypesEntryR\nfieldTypes\x1a=\n\x0f\x46ieldTypesEntry\x12\x10\n\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n\x05value\x18\x02 \x01(\x05R\x05value:\x02\x38\x01\"n\n\rContextShapes\x12,\n\x06shapes\x18\x01 \x03(\x0b\x32\x14.prefab.ContextShapeR\x06shapes\x12!\n\tnamespace\x18\x02 \x01(\tH\x00R\tnamespace\x88\x01\x01\x42\x0c\n\n_namespace\"T\n\rEvaluatedKeys\x12\x12\n\x04keys\x18\x01 \x03(\tR\x04keys\x12!\n\tnamespace\x18\x02 \x01(\tH\x00R\tnamespace\x88\x01\x01\x42\x0c\n\n_namespace\"\xc3\x01\n\x0f\x45valuatedConfig\x12\x10\n\x03key\x18\x01 \x01(\tR\x03key\x12%\n\x0e\x63onfig_version\x18\x02 \x01(\x03R\rconfigVersion\x12+\n\x06result\x18\x03 \x01(\x0b\x32\x13.prefab.ConfigValueR\x06result\x12,\n\x07\x63ontext\x18\x04 \x01(\x0b\x32\x12.prefab.ContextSetR\x07\x63ontext\x12\x1c\n\ttimestamp\x18\x05 \x01(\x03R\ttimestamp\"E\n\x10\x45valuatedConfigs\x12\x31\n\x07\x63onfigs\x18\x01 \x03(\x0b\x32\x17.prefab.EvaluatedConfigR\x07\x63onfigs\"\xb6\x04\n\x17\x43onfigEvaluationCounter\x12\x14\n\x05\x63ount\x18\x01 \x01(\x03R\x05\x63ount\x12 \n\tconfig_id\x18\x02 \x01(\x03H\x00R\x08\x63onfigId\x88\x01\x01\x12*\n\x0eselected_index\x18\x03 \x01(\rH\x01R\rselectedIndex\x88\x01\x01\x12?\n\x0eselected_value\x18\x04 \x01(\x0b\x32\x13.prefab.ConfigValueH\x02R\rselectedValue\x88\x01\x01\x12-\n\x10\x63onfig_row_index\x18\x05 \x01(\rH\x03R\x0e\x63onfigRowIndex\x88\x01\x01\x12;\n\x17\x63onditional_value_index\x18\x06 \x01(\rH\x04R\x15\x63onditionalValueIndex\x88\x01\x01\x12\x35\n\x14weighted_value_index\x18\x07 \x01(\rH\x05R\x12weightedValueIndex\x88\x01\x01\x12>\n\x06reason\x18\x08 \x01(\x0e\x32&.prefab.ConfigEvaluationCounter.ReasonR\x06reason\"\x15\n\x06Reason\x12\x0b\n\x07UNKNOWN\x10\x00\x42\x0c\n\n_config_idB\x11\n\x0f_selected_indexB\x11\n\x0f_selected_valueB\x13\n\x11_config_row_indexB\x1a\n\x18_conditional_value_indexB\x17\n\x15_weighted_value_index\"\x90\x01\n\x17\x43onfigEvaluationSummary\x12\x10\n\x03key\x18\x01 \x01(\tR\x03key\x12&\n\x04type\x18\x02 \x01(\x0e\x32\x12.prefab.ConfigTypeR\x04type\x12;\n\x08\x63ounters\x18\x03 \x03(\x0b\x32\x1f.prefab.ConfigEvaluationCounterR\x08\x63ounters\"\x82\x01\n\x19\x43onfigEvaluationSummaries\x12\x14\n\x05start\x18\x01 \x01(\x03R\x05start\x12\x10\n\x03\x65nd\x18\x02 \x01(\x03R\x03\x65nd\x12=\n\tsummaries\x18\x03 \x03(\x0b\x32\x1f.prefab.ConfigEvaluationSummaryR\tsummaries\"s\n\x15LoggersTelemetryEvent\x12(\n\x07loggers\x18\x01 \x03(\x0b\x32\x0e.prefab.LoggerR\x07loggers\x12\x19\n\x08start_at\x18\x02 \x01(\x03R\x07startAt\x12\x15\n\x06\x65nd_at\x18\x03 \x01(\x03R\x05\x65ndAt\"\xd9\x02\n\x0eTelemetryEvent\x12\x41\n\tsummaries\x18\x02 \x01(\x0b\x32!.prefab.ConfigEvaluationSummariesH\x00R\tsummaries\x12\x44\n\x10\x65xample_contexts\x18\x03 \x01(\x0b\x32\x17.prefab.ExampleContextsH\x00R\x0f\x65xampleContexts\x12\x38\n\x0c\x63lient_stats\x18\x04 \x01(\x0b\x32\x13.prefab.ClientStatsH\x00R\x0b\x63lientStats\x12\x39\n\x07loggers\x18\x05 \x01(\x0b\x32\x1d.prefab.LoggersTelemetryEventH\x00R\x07loggers\x12>\n\x0e\x63ontext_shapes\x18\x06 \x01(\x0b\x32\x15.prefab.ContextShapesH\x00R\rcontextShapesB\t\n\x07payload\"f\n\x0fTelemetryEvents\x12#\n\rinstance_hash\x18\x01 \x01(\tR\x0cinstanceHash\x12.\n\x06\x65vents\x18\x02 \x03(\x0b\x32\x16.prefab.TelemetryEventR\x06\x65vents\"3\n\x17TelemetryEventsResponse\x12\x18\n\x07success\x18\x01 \x01(\x08R\x07success\"E\n\x0f\x45xampleContexts\x12\x32\n\x08\x65xamples\x18\x01 \x03(\x0b\x32\x16.prefab.ExampleContextR\x08\x65xamples\"b\n\x0e\x45xampleContext\x12\x1c\n\ttimestamp\x18\x01 \x01(\x03R\ttimestamp\x12\x32\n\ncontextSet\x18\x02 \x01(\x0b\x32\x12.prefab.ContextSetR\ncontextSet\"e\n\x0b\x43lientStats\x12\x14\n\x05start\x18\x01 \x01(\x03R\x05start\x12\x10\n\x03\x65nd\x18\x02 \x01(\x03R\x03\x65nd\x12.\n\x13\x64ropped_event_count\x18\x03 \x01(\x04R\x11\x64roppedEventCount\"\x91\x01\n\x06Schema\x12\x16\n\x06schema\x18\x01 \x01(\tR\x06schema\x12:\n\x0bschema_type\x18\x02 \x01(\x0e\x32\x19.prefab.Schema.SchemaTypeR\nschemaType\"3\n\nSchemaType\x12\x0b\n\x07UNKNOWN\x10\x00\x12\x07\n\x03ZOD\x10\x01\x12\x0f\n\x0bJSON_SCHEMA\x10\x02*:\n\x0eProvidedSource\x12\x1b\n\x17PROVIDED_SOURCE_NOT_SET\x10\x00\x12\x0b\n\x07\x45NV_VAR\x10\x01*\xa0\x01\n\nConfigType\x12\x17\n\x13NOT_SET_CONFIG_TYPE\x10\x00\x12\n\n\x06\x43ONFIG\x10\x01\x12\x10\n\x0c\x46\x45\x41TURE_FLAG\x10\x02\x12\r\n\tLOG_LEVEL\x10\x03\x12\x0b\n\x07SEGMENT\x10\x04\x12\x14\n\x10LIMIT_DEFINITION\x10\x05\x12\x0b\n\x07\x44\x45LETED\x10\x06\x12\n\n\x06SCHEMA\x10\x07\x12\x10\n\x0cLOG_LEVEL_V2\x10\x08*a\n\x08LogLevel\x12\x15\n\x11NOT_SET_LOG_LEVEL\x10\x00\x12\t\n\x05TRACE\x10\x01\x12\t\n\x05\x44\x45\x42UG\x10\x02\x12\x08\n\x04INFO\x10\x03\x12\x08\n\x04WARN\x10\x05\x12\t\n\x05\x45RROR\x10\x06\x12\t\n\x05\x46\x41TAL\x10\t*G\n\tOnFailure\x12\x0b\n\x07NOT_SET\x10\x00\x12\x10\n\x0cLOG_AND_PASS\x10\x01\x12\x10\n\x0cLOG_AND_FAIL\x10\x02\x12\t\n\x05THROW\x10\x03\x42L\n\x13\x63loud.prefab.domainB\x06PrefabZ-github.com/prefab-cloud/prefab-cloud-go/protob\x06proto3') _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) @@ -32,140 +32,140 @@ _CONFIGEVALUATIONS_VALUESENTRY._serialized_options = b'8\001' _CONTEXTSHAPE_FIELDTYPESENTRY._options = None _CONTEXTSHAPE_FIELDTYPESENTRY._serialized_options = b'8\001' - _globals['_PROVIDEDSOURCE']._serialized_start=10951 - _globals['_PROVIDEDSOURCE']._serialized_end=11009 - _globals['_CONFIGTYPE']._serialized_start=11012 - _globals['_CONFIGTYPE']._serialized_end=11154 - _globals['_LOGLEVEL']._serialized_start=11156 - _globals['_LOGLEVEL']._serialized_end=11253 - _globals['_ONFAILURE']._serialized_start=11255 - _globals['_ONFAILURE']._serialized_end=11326 + _globals['_PROVIDEDSOURCE']._serialized_start=11077 + _globals['_PROVIDEDSOURCE']._serialized_end=11135 + _globals['_CONFIGTYPE']._serialized_start=11138 + _globals['_CONFIGTYPE']._serialized_end=11298 + _globals['_LOGLEVEL']._serialized_start=11300 + _globals['_LOGLEVEL']._serialized_end=11397 + _globals['_ONFAILURE']._serialized_start=11399 + _globals['_ONFAILURE']._serialized_end=11470 _globals['_CONFIGSERVICEPOINTER']._serialized_start=24 _globals['_CONFIGSERVICEPOINTER']._serialized_end=147 _globals['_CONFIGVALUE']._serialized_start=150 - _globals['_CONFIGVALUE']._serialized_end=871 - _globals['_JSON']._serialized_start=873 - _globals['_JSON']._serialized_end=899 - _globals['_ISODURATION']._serialized_start=901 - _globals['_ISODURATION']._serialized_end=946 - _globals['_PROVIDED']._serialized_start=948 - _globals['_PROVIDED']._serialized_end=1062 - _globals['_INTRANGE']._serialized_start=1064 - _globals['_INTRANGE']._serialized_end=1142 - _globals['_STRINGLIST']._serialized_start=1144 - _globals['_STRINGLIST']._serialized_end=1180 - _globals['_WEIGHTEDVALUE']._serialized_start=1182 - _globals['_WEIGHTEDVALUE']._serialized_end=1264 - _globals['_WEIGHTEDVALUES']._serialized_start=1267 - _globals['_WEIGHTEDVALUES']._serialized_end=1429 - _globals['_APIKEYMETADATA']._serialized_start=1431 - _globals['_APIKEYMETADATA']._serialized_end=1534 - _globals['_CONFIGS']._serialized_start=1537 - _globals['_CONFIGS']._serialized_end=1899 - _globals['_CONFIG']._serialized_start=1902 - _globals['_CONFIG']._serialized_end=2564 - _globals['_CONFIG_VALUETYPE']._serialized_start=2354 - _globals['_CONFIG_VALUETYPE']._serialized_end=2536 - _globals['_CHANGEDBY']._serialized_start=2566 - _globals['_CHANGEDBY']._serialized_end=2654 - _globals['_CONFIGROW']._serialized_start=2657 - _globals['_CONFIGROW']._serialized_end=2931 - _globals['_CONFIGROW_PROPERTIESENTRY']._serialized_start=2830 - _globals['_CONFIGROW_PROPERTIESENTRY']._serialized_end=2912 - _globals['_CONDITIONALVALUE']._serialized_start=2933 - _globals['_CONDITIONALVALUE']._serialized_end=3041 - _globals['_CRITERION']._serialized_start=3044 - _globals['_CRITERION']._serialized_end=3872 - _globals['_CRITERION_CRITERIONOPERATOR']._serialized_start=3219 - _globals['_CRITERION_CRITERIONOPERATOR']._serialized_end=3872 - _globals['_LOGGERS']._serialized_start=3875 - _globals['_LOGGERS']._serialized_end=4062 - _globals['_LOGGER']._serialized_start=4065 - _globals['_LOGGER']._serialized_end=4340 - _globals['_LOGGERREPORTRESPONSE']._serialized_start=4342 - _globals['_LOGGERREPORTRESPONSE']._serialized_end=4364 - _globals['_LIMITRESPONSE']._serialized_start=4367 - _globals['_LIMITRESPONSE']._serialized_end=4964 - _globals['_LIMITRESPONSE_LIMITPOLICYNAMES']._serialized_start=4795 - _globals['_LIMITRESPONSE_LIMITPOLICYNAMES']._serialized_end=4964 - _globals['_LIMITREQUEST']._serialized_start=4967 - _globals['_LIMITREQUEST']._serialized_end=5332 - _globals['_LIMITREQUEST_LIMITCOMBINER']._serialized_start=5278 - _globals['_LIMITREQUEST_LIMITCOMBINER']._serialized_end=5332 - _globals['_CONTEXTSET']._serialized_start=5334 - _globals['_CONTEXTSET']._serialized_end=5391 - _globals['_CONTEXT']._serialized_start=5394 - _globals['_CONTEXT']._serialized_end=5570 - _globals['_CONTEXT_VALUESENTRY']._serialized_start=5483 - _globals['_CONTEXT_VALUESENTRY']._serialized_end=5561 - _globals['_IDENTITY']._serialized_start=5573 - _globals['_IDENTITY']._serialized_end=5752 - _globals['_IDENTITY_ATTRIBUTESENTRY']._serialized_start=5680 - _globals['_IDENTITY_ATTRIBUTESENTRY']._serialized_end=5741 - _globals['_CONFIGEVALUATIONMETADATA']._serialized_start=5755 - _globals['_CONFIGEVALUATIONMETADATA']._serialized_end=6177 - _globals['_CLIENTCONFIGVALUE']._serialized_start=6180 - _globals['_CLIENTCONFIGVALUE']._serialized_end=6676 - _globals['_CLIENTDURATION']._serialized_start=6678 - _globals['_CLIENTDURATION']._serialized_end=6774 - _globals['_CONFIGEVALUATIONS']._serialized_start=6777 - _globals['_CONFIGEVALUATIONS']._serialized_end=7121 - _globals['_CONFIGEVALUATIONS_VALUESENTRY']._serialized_start=6997 - _globals['_CONFIGEVALUATIONS_VALUESENTRY']._serialized_end=7081 - _globals['_LIMITDEFINITION']._serialized_start=7124 - _globals['_LIMITDEFINITION']._serialized_end=7496 - _globals['_LIMITDEFINITION_SAFETYLEVEL']._serialized_start=7432 - _globals['_LIMITDEFINITION_SAFETYLEVEL']._serialized_end=7496 - _globals['_LIMITDEFINITIONS']._serialized_start=7498 - _globals['_LIMITDEFINITIONS']._serialized_end=7575 - _globals['_BUFFEREDREQUEST']._serialized_start=7578 - _globals['_BUFFEREDREQUEST']._serialized_end=7778 - _globals['_BATCHREQUEST']._serialized_start=7781 - _globals['_BATCHREQUEST']._serialized_end=8003 - _globals['_BASICRESPONSE']._serialized_start=8005 - _globals['_BASICRESPONSE']._serialized_end=8046 - _globals['_CREATIONRESPONSE']._serialized_start=8048 - _globals['_CREATIONRESPONSE']._serialized_end=8115 - _globals['_IDBLOCK']._serialized_start=8118 - _globals['_IDBLOCK']._serialized_end=8273 - _globals['_IDBLOCKREQUEST']._serialized_start=8276 - _globals['_IDBLOCKREQUEST']._serialized_end=8418 - _globals['_CONTEXTSHAPE']._serialized_start=8421 - _globals['_CONTEXTSHAPE']._serialized_end=8589 - _globals['_CONTEXTSHAPE_FIELDTYPESENTRY']._serialized_start=8528 - _globals['_CONTEXTSHAPE_FIELDTYPESENTRY']._serialized_end=8589 - _globals['_CONTEXTSHAPES']._serialized_start=8591 - _globals['_CONTEXTSHAPES']._serialized_end=8701 - _globals['_EVALUATEDKEYS']._serialized_start=8703 - _globals['_EVALUATEDKEYS']._serialized_end=8787 - _globals['_EVALUATEDCONFIG']._serialized_start=8790 - _globals['_EVALUATEDCONFIG']._serialized_end=8985 - _globals['_EVALUATEDCONFIGS']._serialized_start=8987 - _globals['_EVALUATEDCONFIGS']._serialized_end=9056 - _globals['_CONFIGEVALUATIONCOUNTER']._serialized_start=9059 - _globals['_CONFIGEVALUATIONCOUNTER']._serialized_end=9625 - _globals['_CONFIGEVALUATIONCOUNTER_REASON']._serialized_start=9478 - _globals['_CONFIGEVALUATIONCOUNTER_REASON']._serialized_end=9499 - _globals['_CONFIGEVALUATIONSUMMARY']._serialized_start=9628 - _globals['_CONFIGEVALUATIONSUMMARY']._serialized_end=9772 - _globals['_CONFIGEVALUATIONSUMMARIES']._serialized_start=9775 - _globals['_CONFIGEVALUATIONSUMMARIES']._serialized_end=9905 - _globals['_LOGGERSTELEMETRYEVENT']._serialized_start=9907 - _globals['_LOGGERSTELEMETRYEVENT']._serialized_end=10022 - _globals['_TELEMETRYEVENT']._serialized_start=10025 - _globals['_TELEMETRYEVENT']._serialized_end=10370 - _globals['_TELEMETRYEVENTS']._serialized_start=10372 - _globals['_TELEMETRYEVENTS']._serialized_end=10474 - _globals['_TELEMETRYEVENTSRESPONSE']._serialized_start=10476 - _globals['_TELEMETRYEVENTSRESPONSE']._serialized_end=10527 - _globals['_EXAMPLECONTEXTS']._serialized_start=10529 - _globals['_EXAMPLECONTEXTS']._serialized_end=10598 - _globals['_EXAMPLECONTEXT']._serialized_start=10600 - _globals['_EXAMPLECONTEXT']._serialized_end=10698 - _globals['_CLIENTSTATS']._serialized_start=10700 - _globals['_CLIENTSTATS']._serialized_end=10801 - _globals['_SCHEMA']._serialized_start=10804 - _globals['_SCHEMA']._serialized_end=10949 - _globals['_SCHEMA_SCHEMATYPE']._serialized_start=10898 - _globals['_SCHEMA_SCHEMATYPE']._serialized_end=10949 + _globals['_CONFIGVALUE']._serialized_end=960 + _globals['_JSON']._serialized_start=962 + _globals['_JSON']._serialized_end=988 + _globals['_ISODURATION']._serialized_start=990 + _globals['_ISODURATION']._serialized_end=1035 + _globals['_PROVIDED']._serialized_start=1037 + _globals['_PROVIDED']._serialized_end=1151 + _globals['_INTRANGE']._serialized_start=1153 + _globals['_INTRANGE']._serialized_end=1231 + _globals['_STRINGLIST']._serialized_start=1233 + _globals['_STRINGLIST']._serialized_end=1269 + _globals['_WEIGHTEDVALUE']._serialized_start=1271 + _globals['_WEIGHTEDVALUE']._serialized_end=1353 + _globals['_WEIGHTEDVALUES']._serialized_start=1356 + _globals['_WEIGHTEDVALUES']._serialized_end=1518 + _globals['_APIKEYMETADATA']._serialized_start=1520 + _globals['_APIKEYMETADATA']._serialized_end=1623 + _globals['_CONFIGS']._serialized_start=1626 + _globals['_CONFIGS']._serialized_end=1988 + _globals['_CONFIG']._serialized_start=1991 + _globals['_CONFIG']._serialized_end=2653 + _globals['_CONFIG_VALUETYPE']._serialized_start=2443 + _globals['_CONFIG_VALUETYPE']._serialized_end=2625 + _globals['_CHANGEDBY']._serialized_start=2655 + _globals['_CHANGEDBY']._serialized_end=2780 + _globals['_CONFIGROW']._serialized_start=2783 + _globals['_CONFIGROW']._serialized_end=3057 + _globals['_CONFIGROW_PROPERTIESENTRY']._serialized_start=2956 + _globals['_CONFIGROW_PROPERTIESENTRY']._serialized_end=3038 + _globals['_CONDITIONALVALUE']._serialized_start=3059 + _globals['_CONDITIONALVALUE']._serialized_end=3167 + _globals['_CRITERION']._serialized_start=3170 + _globals['_CRITERION']._serialized_end=3998 + _globals['_CRITERION_CRITERIONOPERATOR']._serialized_start=3345 + _globals['_CRITERION_CRITERIONOPERATOR']._serialized_end=3998 + _globals['_LOGGERS']._serialized_start=4001 + _globals['_LOGGERS']._serialized_end=4188 + _globals['_LOGGER']._serialized_start=4191 + _globals['_LOGGER']._serialized_end=4466 + _globals['_LOGGERREPORTRESPONSE']._serialized_start=4468 + _globals['_LOGGERREPORTRESPONSE']._serialized_end=4490 + _globals['_LIMITRESPONSE']._serialized_start=4493 + _globals['_LIMITRESPONSE']._serialized_end=5090 + _globals['_LIMITRESPONSE_LIMITPOLICYNAMES']._serialized_start=4921 + _globals['_LIMITRESPONSE_LIMITPOLICYNAMES']._serialized_end=5090 + _globals['_LIMITREQUEST']._serialized_start=5093 + _globals['_LIMITREQUEST']._serialized_end=5458 + _globals['_LIMITREQUEST_LIMITCOMBINER']._serialized_start=5404 + _globals['_LIMITREQUEST_LIMITCOMBINER']._serialized_end=5458 + _globals['_CONTEXTSET']._serialized_start=5460 + _globals['_CONTEXTSET']._serialized_end=5517 + _globals['_CONTEXT']._serialized_start=5520 + _globals['_CONTEXT']._serialized_end=5696 + _globals['_CONTEXT_VALUESENTRY']._serialized_start=5609 + _globals['_CONTEXT_VALUESENTRY']._serialized_end=5687 + _globals['_IDENTITY']._serialized_start=5699 + _globals['_IDENTITY']._serialized_end=5878 + _globals['_IDENTITY_ATTRIBUTESENTRY']._serialized_start=5806 + _globals['_IDENTITY_ATTRIBUTESENTRY']._serialized_end=5867 + _globals['_CONFIGEVALUATIONMETADATA']._serialized_start=5881 + _globals['_CONFIGEVALUATIONMETADATA']._serialized_end=6303 + _globals['_CLIENTCONFIGVALUE']._serialized_start=6306 + _globals['_CLIENTCONFIGVALUE']._serialized_end=6802 + _globals['_CLIENTDURATION']._serialized_start=6804 + _globals['_CLIENTDURATION']._serialized_end=6900 + _globals['_CONFIGEVALUATIONS']._serialized_start=6903 + _globals['_CONFIGEVALUATIONS']._serialized_end=7247 + _globals['_CONFIGEVALUATIONS_VALUESENTRY']._serialized_start=7123 + _globals['_CONFIGEVALUATIONS_VALUESENTRY']._serialized_end=7207 + _globals['_LIMITDEFINITION']._serialized_start=7250 + _globals['_LIMITDEFINITION']._serialized_end=7622 + _globals['_LIMITDEFINITION_SAFETYLEVEL']._serialized_start=7558 + _globals['_LIMITDEFINITION_SAFETYLEVEL']._serialized_end=7622 + _globals['_LIMITDEFINITIONS']._serialized_start=7624 + _globals['_LIMITDEFINITIONS']._serialized_end=7701 + _globals['_BUFFEREDREQUEST']._serialized_start=7704 + _globals['_BUFFEREDREQUEST']._serialized_end=7904 + _globals['_BATCHREQUEST']._serialized_start=7907 + _globals['_BATCHREQUEST']._serialized_end=8129 + _globals['_BASICRESPONSE']._serialized_start=8131 + _globals['_BASICRESPONSE']._serialized_end=8172 + _globals['_CREATIONRESPONSE']._serialized_start=8174 + _globals['_CREATIONRESPONSE']._serialized_end=8241 + _globals['_IDBLOCK']._serialized_start=8244 + _globals['_IDBLOCK']._serialized_end=8399 + _globals['_IDBLOCKREQUEST']._serialized_start=8402 + _globals['_IDBLOCKREQUEST']._serialized_end=8544 + _globals['_CONTEXTSHAPE']._serialized_start=8547 + _globals['_CONTEXTSHAPE']._serialized_end=8715 + _globals['_CONTEXTSHAPE_FIELDTYPESENTRY']._serialized_start=8654 + _globals['_CONTEXTSHAPE_FIELDTYPESENTRY']._serialized_end=8715 + _globals['_CONTEXTSHAPES']._serialized_start=8717 + _globals['_CONTEXTSHAPES']._serialized_end=8827 + _globals['_EVALUATEDKEYS']._serialized_start=8829 + _globals['_EVALUATEDKEYS']._serialized_end=8913 + _globals['_EVALUATEDCONFIG']._serialized_start=8916 + _globals['_EVALUATEDCONFIG']._serialized_end=9111 + _globals['_EVALUATEDCONFIGS']._serialized_start=9113 + _globals['_EVALUATEDCONFIGS']._serialized_end=9182 + _globals['_CONFIGEVALUATIONCOUNTER']._serialized_start=9185 + _globals['_CONFIGEVALUATIONCOUNTER']._serialized_end=9751 + _globals['_CONFIGEVALUATIONCOUNTER_REASON']._serialized_start=9604 + _globals['_CONFIGEVALUATIONCOUNTER_REASON']._serialized_end=9625 + _globals['_CONFIGEVALUATIONSUMMARY']._serialized_start=9754 + _globals['_CONFIGEVALUATIONSUMMARY']._serialized_end=9898 + _globals['_CONFIGEVALUATIONSUMMARIES']._serialized_start=9901 + _globals['_CONFIGEVALUATIONSUMMARIES']._serialized_end=10031 + _globals['_LOGGERSTELEMETRYEVENT']._serialized_start=10033 + _globals['_LOGGERSTELEMETRYEVENT']._serialized_end=10148 + _globals['_TELEMETRYEVENT']._serialized_start=10151 + _globals['_TELEMETRYEVENT']._serialized_end=10496 + _globals['_TELEMETRYEVENTS']._serialized_start=10498 + _globals['_TELEMETRYEVENTS']._serialized_end=10600 + _globals['_TELEMETRYEVENTSRESPONSE']._serialized_start=10602 + _globals['_TELEMETRYEVENTSRESPONSE']._serialized_end=10653 + _globals['_EXAMPLECONTEXTS']._serialized_start=10655 + _globals['_EXAMPLECONTEXTS']._serialized_end=10724 + _globals['_EXAMPLECONTEXT']._serialized_start=10726 + _globals['_EXAMPLECONTEXT']._serialized_end=10824 + _globals['_CLIENTSTATS']._serialized_start=10826 + _globals['_CLIENTSTATS']._serialized_end=10927 + _globals['_SCHEMA']._serialized_start=10930 + _globals['_SCHEMA']._serialized_end=11075 + _globals['_SCHEMA_SCHEMATYPE']._serialized_start=11024 + _globals['_SCHEMA_SCHEMATYPE']._serialized_end=11075 # @@protoc_insertion_point(module_scope) diff --git a/prefab_pb2.pyi b/prefab_pb2.pyi index e9e1757..12fb5f9 100644 --- a/prefab_pb2.pyi +++ b/prefab_pb2.pyi @@ -56,6 +56,7 @@ class _ConfigTypeEnumTypeWrapper( LIMIT_DEFINITION: _ConfigType.ValueType # 5 DELETED: _ConfigType.ValueType # 6 SCHEMA: _ConfigType.ValueType # 7 + LOG_LEVEL_V2: _ConfigType.ValueType # 8 class ConfigType(_ConfigType, metaclass=_ConfigTypeEnumTypeWrapper): ... @@ -68,6 +69,7 @@ SEGMENT: ConfigType.ValueType # 4 LIMIT_DEFINITION: ConfigType.ValueType # 5 DELETED: ConfigType.ValueType # 6 SCHEMA: ConfigType.ValueType # 7 +LOG_LEVEL_V2: ConfigType.ValueType # 8 global___ConfigType = ConfigType class _LogLevel: @@ -179,6 +181,8 @@ class ConfigValue(google.protobuf.message.Message): SCHEMA_FIELD_NUMBER: builtins.int CONFIDENTIAL_FIELD_NUMBER: builtins.int DECRYPT_WITH_FIELD_NUMBER: builtins.int + NAME_FIELD_NUMBER: builtins.int + DESCRIPTION_FIELD_NUMBER: builtins.int int: builtins.int string: builtins.str bytes: builtins.bytes @@ -205,6 +209,10 @@ class ConfigValue(google.protobuf.message.Message): """don't log or telemetry this value""" decrypt_with: builtins.str """key name to decrypt with""" + name: builtins.str + """used for naming the allowable values for feature flags""" + description: builtins.str + """used for naming the allowable values for feature flags""" def __init__( self, *, @@ -224,6 +232,8 @@ class ConfigValue(google.protobuf.message.Message): schema: global___Schema | None = ..., confidential: builtins.bool | None = ..., decrypt_with: builtins.str | None = ..., + name: builtins.str | None = ..., + description: builtins.str | None = ..., ) -> None: ... def HasField( self, @@ -232,6 +242,10 @@ class ConfigValue(google.protobuf.message.Message): b"_confidential", "_decrypt_with", b"_decrypt_with", + "_description", + b"_description", + "_name", + b"_name", "bool", b"bool", "bytes", @@ -240,6 +254,8 @@ class ConfigValue(google.protobuf.message.Message): b"confidential", "decrypt_with", b"decrypt_with", + "description", + b"description", "double", b"double", "duration", @@ -254,6 +270,8 @@ class ConfigValue(google.protobuf.message.Message): b"limit_definition", "log_level", b"log_level", + "name", + b"name", "provided", b"provided", "schema", @@ -275,6 +293,10 @@ class ConfigValue(google.protobuf.message.Message): b"_confidential", "_decrypt_with", b"_decrypt_with", + "_description", + b"_description", + "_name", + b"_name", "bool", b"bool", "bytes", @@ -283,6 +305,8 @@ class ConfigValue(google.protobuf.message.Message): b"confidential", "decrypt_with", b"decrypt_with", + "description", + b"description", "double", b"double", "duration", @@ -297,6 +321,8 @@ class ConfigValue(google.protobuf.message.Message): b"limit_definition", "log_level", b"log_level", + "name", + b"name", "provided", b"provided", "schema", @@ -320,6 +346,14 @@ class ConfigValue(google.protobuf.message.Message): self, oneof_group: typing_extensions.Literal["_decrypt_with", b"_decrypt_with"] ) -> typing_extensions.Literal["decrypt_with"] | None: ... @typing.overload + def WhichOneof( + self, oneof_group: typing_extensions.Literal["_description", b"_description"] + ) -> typing_extensions.Literal["description"] | None: ... + @typing.overload + def WhichOneof( + self, oneof_group: typing_extensions.Literal["_name", b"_name"] + ) -> typing_extensions.Literal["name"] | None: ... + @typing.overload def WhichOneof( self, oneof_group: typing_extensions.Literal["type", b"type"] ) -> ( @@ -864,20 +898,30 @@ class ChangedBy(google.protobuf.message.Message): USER_ID_FIELD_NUMBER: builtins.int EMAIL_FIELD_NUMBER: builtins.int API_KEY_ID_FIELD_NUMBER: builtins.int + USER_IDENTITY_FIELD_NUMBER: builtins.int user_id: builtins.int email: builtins.str api_key_id: builtins.str + user_identity: builtins.str def __init__( self, *, user_id: builtins.int = ..., email: builtins.str = ..., api_key_id: builtins.str = ..., + user_identity: builtins.str = ..., ) -> None: ... def ClearField( self, field_name: typing_extensions.Literal[ - "api_key_id", b"api_key_id", "email", b"email", "user_id", b"user_id" + "api_key_id", + b"api_key_id", + "email", + b"email", + "user_id", + b"user_id", + "user_identity", + b"user_identity", ], ) -> None: ... diff --git a/pyproject.toml b/pyproject.toml index 3028dfb..1a70186 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "sdk-reforge" -version = "1.0.1" +version = "1.1.0" description = "Python sdk for Reforge Feature Flags and Config as a Service: https://www.reforge.com" license = "MIT" authors = ["Michael Berkowitz ", "James Kebinger "] @@ -18,12 +18,15 @@ python = ">= 3.9, < 4" pyyaml = "^6.0.0" mmh3 = ">=3.0.0,<5.0.0" requests = ">= 2.30.0" -structlog = ">= 21.1.0" sseclient-py = "^1.7.2" cachetools = "^5.3.0" protobuf = ">= 4.21.0" isodate = "^0.6.1" tenacity = ">=8.0.0" +structlog = {version = ">= 21.1.0", optional = true} + +[tool.poetry.extras] +structlog = ["structlog"] [tool.poetry.group.dev.dependencies] pytest = "^7.2.1" @@ -32,6 +35,7 @@ mypy = "^1.4.1" pre-commit = "^3.5.0" responses = "^0.24.1" types-requests = "^2.31.0" +structlog = ">= 21.1.0" [build-system] requires = ["poetry-core"] diff --git a/sdk_reforge/VERSION b/sdk_reforge/VERSION index 7dea76e..9084fa2 100644 --- a/sdk_reforge/VERSION +++ b/sdk_reforge/VERSION @@ -1 +1 @@ -1.0.1 +1.1.0 diff --git a/sdk_reforge/__init__.py b/sdk_reforge/__init__.py index d6c64f5..4c6414f 100644 --- a/sdk_reforge/__init__.py +++ b/sdk_reforge/__init__.py @@ -7,6 +7,9 @@ - ReforgeSDK: The main SDK for interacting with Reforge - Options: Configuration options for the SDK - Context: Context information for evaluating configs and feature flags +- LogLevel: Enumeration of log levels for logging configuration +- LoggerFilter: Filter for standard Python logging integration +- LoggerProcessor: Processor for structlog integration Re-exported Protocol Buffer types: - ConfigValue: Represents a configuration value @@ -14,7 +17,6 @@ - ProtoContext: Protocol buffer Context class - ContextSet: Collection of contexts - ContextShape: Shape information for contexts -- LogLevel: Enumeration of log levels - Json: Represents JSON data in configuration values - Schema: Represents schema validation for configuration values """ @@ -29,6 +31,8 @@ from .context import Context, NamedContext from .feature_flag_sdk import FeatureFlagSDK from .config_sdk import ConfigSDK +from .log_level import LogLevel +from .logging import LoggerFilter, LoggerProcessor from .constants import ( ConfigValueType, ContextDictType, @@ -44,11 +48,12 @@ Context as ProtoContext, ContextSet, ContextShape, - LogLevel, Json, Schema, ) +# Note: LogLevel is imported from .log_level, not from prefab_pb2 + log = _internal_logging.InternalLogger(__name__) diff --git a/sdk_reforge/log_level.py b/sdk_reforge/log_level.py new file mode 100644 index 0000000..15a7b3c --- /dev/null +++ b/sdk_reforge/log_level.py @@ -0,0 +1,24 @@ +"""Log level enum for Reforge SDK""" + +from enum import Enum +import logging + + +class LogLevel(Enum): + """Log levels supported by Reforge SDK""" + + TRACE = logging.DEBUG # Python doesn't have TRACE, map to DEBUG + DEBUG = logging.DEBUG + INFO = logging.INFO + WARN = logging.WARNING + ERROR = logging.ERROR + FATAL = logging.CRITICAL + + @property + def python_level(self) -> int: + """Get the Python logging level for this log level""" + return self.value + + def __int__(self) -> int: + """Convert to integer (Python logging level)""" + return self.value diff --git a/sdk_reforge/logging.py b/sdk_reforge/logging.py new file mode 100644 index 0000000..0c86046 --- /dev/null +++ b/sdk_reforge/logging.py @@ -0,0 +1,207 @@ +""" +Logging integrations for Reforge SDK. + +Provides filters and processors for standard logging and structlog that dynamically +adjust log levels based on Reforge configuration. +""" + +import logging +from typing import Optional, Any + +try: + from structlog import DropEvent + + STRUCTLOG_AVAILABLE = True +except ImportError: + STRUCTLOG_AVAILABLE = False + DropEvent = None + +import sdk_reforge +from sdk_reforge import ReforgeSDK + + +class BaseLoggerFilterProcessor: + """Base class for logger filters/processors""" + + def __init__(self, sdk: Optional[ReforgeSDK] = None) -> None: + self.sdk = sdk + + def _get_sdk(self) -> Optional[ReforgeSDK]: + """Get SDK instance, either from constructor or singleton""" + if self.sdk: + return self.sdk + try: + return sdk_reforge.get_sdk() + except Exception: + return None + + def _should_log_message( + self, sdk: ReforgeSDK, logger_name: str, called_method_level: int + ) -> bool: + """Check if message should be logged based on configured level""" + log_level = sdk.get_log_level(logger_name) + return called_method_level >= log_level.python_level + + +class LoggerFilter(BaseLoggerFilterProcessor, logging.Filter): + """ + Filter for use with standard Python logging. + + This filter dynamically adjusts log levels based on Reforge configuration. + Will get its SDK reference from sdk_reforge.get_sdk() unless overridden. + + Example usage: + import logging + from sdk_reforge.logging import LoggerFilter + + logger = logging.getLogger("my.app") + logger.addFilter(LoggerFilter()) + """ + + def __init__(self, sdk: Optional[ReforgeSDK] = None) -> None: + super().__init__(sdk) + + def logger_name(self, record: logging.LogRecord) -> str: + """ + Override this method to derive a different logger name from the record. + + Args: + record: The log record + + Returns: + str: The logger name to use for level lookup + """ + return record.name + + def filter(self, record: logging.LogRecord) -> bool: + """ + Determine if the log record should be logged. + + Args: + record: The log record to filter + + Returns: + bool: True if the record should be logged, False otherwise + """ + sdk = self._get_sdk() + if sdk: + logger_name = self.logger_name(record) + if logger_name: + return self._should_log_message(sdk, logger_name, record.levelno) + return True + + +if STRUCTLOG_AVAILABLE: + + class LoggerProcessor(BaseLoggerFilterProcessor): + """ + Processor for use with structlog. + + This processor dynamically adjusts log levels based on Reforge configuration. + Will get its SDK reference from sdk_reforge.get_sdk() unless overridden. + + Example usage: + import structlog + from sdk_reforge.logging import LoggerProcessor + + structlog.configure( + processors=[ + structlog.stdlib.add_log_level, + LoggerProcessor().processor, + # ... other processors + ] + ) + """ + + def __init__(self, sdk: Optional[ReforgeSDK] = None) -> None: + super().__init__(sdk) + + def logger_name(self, logger: Any, event_dict: dict) -> Optional[str]: + """ + Override this method to derive a different logger name. + + Args: + logger: The structlog logger instance + event_dict: The event dictionary + + Returns: + Optional[str]: The logger name to use for level lookup + """ + return getattr(logger, "name", None) or event_dict.get("logger") + + def processor(self, logger: Any, method_name: str, event_dict: dict) -> dict: + """ + Process a structlog event, filtering based on configured log level. + + This method depends on structlog.stdlib.add_log_level being in the + structlog pipeline first. + + Args: + logger: The structlog logger instance + method_name: The name of the method called (e.g., "info", "error") + event_dict: The event dictionary + + Returns: + dict: The event dictionary (if not filtered) + + Raises: + DropEvent: If the log should be filtered + """ + logger_name = self.logger_name(logger, event_dict) + called_method_level = self._derive_structlog_numeric_level( + method_name, event_dict + ) + if not called_method_level: + return event_dict + if not logger_name: + return event_dict + + sdk = self._get_sdk() + if sdk: + if not self._should_log_message(sdk, logger_name, called_method_level): + raise DropEvent + return event_dict + + @staticmethod + def _derive_structlog_numeric_level( + method_name: str, event_dict: dict + ) -> Optional[int]: + """ + Derive the numeric log level from structlog event. + + Args: + method_name: The method name called + event_dict: The event dictionary + + Returns: + Optional[int]: The numeric log level, or None if not determinable + """ + # Check for numeric level added by level_to_number processor + numeric_level_from_dict = event_dict.get("level_number") + if type(numeric_level_from_dict) == int: + return numeric_level_from_dict + + # Try to derive from level string or method name + string_level = event_dict.get("level") or method_name + + # Remap levels per structlog conventions + if string_level == "warn": + string_level = "warning" + elif string_level == "exception": + string_level = "error" + + if string_level: + maybe_numeric_level = logging.getLevelName(string_level.upper()) + if type(maybe_numeric_level) == int: + return maybe_numeric_level + return None + +else: + # Structlog not available, create stub class + class LoggerProcessor(BaseLoggerFilterProcessor): # type: ignore + """Stub class when structlog is not available""" + + def __init__(self, sdk: Optional[ReforgeSDK] = None) -> None: + raise ImportError( + "structlog is not installed. Install it with: pip install structlog" + ) diff --git a/sdk_reforge/options.py b/sdk_reforge/options.py index 8d3fafb..fb9f233 100644 --- a/sdk_reforge/options.py +++ b/sdk_reforge/options.py @@ -75,6 +75,7 @@ def __init__( context_upload_mode: ContextUploadMode = ContextUploadMode.PERIODIC_EXAMPLE, global_context: Optional[ContextDictType | Context] = None, on_ready_callback: Optional[Callable[[], None]] = None, + logger_key: str = "log-levels.default", ) -> None: self.reforge_datasources = Options.__validate_datasource(reforge_datasources) self.datafile = x_datafile @@ -109,6 +110,7 @@ def __init__( self.collect_evaluation_summaries = collect_evaluation_summaries self.global_context = Context.normalize_context_arg(global_context) self.on_ready_callback = on_ready_callback + self.logger_key = logger_key def is_local_only(self) -> bool: return self.reforge_datasources == "LOCAL_ONLY" diff --git a/sdk_reforge/sdk.py b/sdk_reforge/sdk.py index 2186c63..770b6c2 100644 --- a/sdk_reforge/sdk.py +++ b/sdk_reforge/sdk.py @@ -11,6 +11,7 @@ from .feature_flag_sdk import FeatureFlagSDK from .options import Options from ._requests import TimeoutHTTPAdapter, VersionHeader, Version +from .log_level import LogLevel from typing import Optional import prefab_pb2 as Prefab import uuid @@ -94,6 +95,50 @@ def is_ff(self, key: str) -> bool: return True return False + def get_log_level(self, logger_name: str) -> LogLevel: + """ + Get the log level for the given logger name. + + This evaluates the config at the logger_key (from Options, default "log-levels.default") + with a context containing the logger name. Returns LogLevel.DEBUG if no config is found. + + Args: + logger_name: The name of the logger to get the level for + + Returns: + LogLevel: The log level for this logger + """ + log_context = { + "reforge-sdk-logging": {"lang": "python", "logger-path": logger_name} + } + + try: + # Get the protobuf LogLevel value from the config + pb_log_level = self.get( + self.options.logger_key, default=None, context=log_context + ) + + if pb_log_level is None: + return LogLevel.DEBUG + + # Map from protobuf LogLevel to our LogLevel enum + if pb_log_level == Prefab.LogLevel.Value("TRACE"): + return LogLevel.TRACE + elif pb_log_level == Prefab.LogLevel.Value("DEBUG"): + return LogLevel.DEBUG + elif pb_log_level == Prefab.LogLevel.Value("INFO"): + return LogLevel.INFO + elif pb_log_level == Prefab.LogLevel.Value("WARN"): + return LogLevel.WARN + elif pb_log_level == Prefab.LogLevel.Value("ERROR"): + return LogLevel.ERROR + elif pb_log_level == Prefab.LogLevel.Value("FATAL"): + return LogLevel.FATAL + else: + return LogLevel.DEBUG + except Exception: + return LogLevel.DEBUG + def context(self) -> Context: return Context.get_current() diff --git a/tests/test_context_shape.py b/tests/test_context_shape.py index 9290d2a..2dc2217 100644 --- a/tests/test_context_shape.py +++ b/tests/test_context_shape.py @@ -41,6 +41,8 @@ def test_mapping_is_exhaustive(self): "decrypt_with", "duration", "schema", + "name", + "description", ] supported = list( diff --git a/tests/test_log_level.py b/tests/test_log_level.py new file mode 100644 index 0000000..726ebef --- /dev/null +++ b/tests/test_log_level.py @@ -0,0 +1,46 @@ +import logging +import os +from contextlib import contextmanager +from sdk_reforge import ReforgeSDK, Options, LogLevel + + +@contextmanager +def extended_env(new_env_vars): # type: ignore[no-untyped-def] + old_env = os.environ.copy() + os.environ.update(new_env_vars) + try: + yield + finally: + os.environ.clear() + os.environ.update(old_env) + + +class TestLogLevel: + def test_log_level_enum_values(self) -> None: + """Test that LogLevel enum has correct Python logging values""" + assert LogLevel.TRACE.python_level == logging.DEBUG + assert LogLevel.DEBUG.python_level == logging.DEBUG + assert LogLevel.INFO.python_level == logging.INFO + assert LogLevel.WARN.python_level == logging.WARNING + assert LogLevel.ERROR.python_level == logging.ERROR + assert LogLevel.FATAL.python_level == logging.CRITICAL + + def test_get_log_level_returns_debug_when_not_found(self) -> None: + """Test that get_log_level returns DEBUG when config not found""" + with extended_env({"REFORGE_DATASOURCES": "LOCAL_ONLY"}): + sdk = ReforgeSDK( + Options( + x_datafile="tests/test.datafile.json", + logger_key="nonexistent.key", + collect_sync_interval=None, + ) + ) + + level = sdk.get_log_level("any.logger") + assert level == LogLevel.DEBUG + + def test_logger_key_default_value(self) -> None: + """Test that logger_key has the correct default value""" + with extended_env({"REFORGE_DATASOURCES": "LOCAL_ONLY"}): + options = Options() + assert options.logger_key == "log-levels.default" diff --git a/tests/test_logging.py b/tests/test_logging.py new file mode 100644 index 0000000..28fb5f7 --- /dev/null +++ b/tests/test_logging.py @@ -0,0 +1,201 @@ +from __future__ import annotations + +import logging +import os +import sys +import re +from contextlib import contextmanager +from typing import Any, Generator, Optional, Tuple + +import pytest +import prefab_pb2 as Prefab + +from sdk_reforge import ReforgeSDK, Options +from sdk_reforge.logging import LoggerFilter, LoggerProcessor + + +@contextmanager +def extended_env(new_env_vars: dict[str, str]) -> Generator[None, None, None]: + old_env = os.environ.copy() + os.environ.update(new_env_vars) + try: + yield + finally: + os.environ.clear() + os.environ.update(old_env) + + +def assert_logged( + cap: Any, level: str, msg: str, logger_name: str, should_log: bool = True +) -> None: + """Check if a message was logged to stdout""" + pattern = re.compile(f".*{logger_name or 'root'}.*{level}.*{msg}.*") + stdout, stderr = cap.readouterr() + if should_log: + assert pattern.match( + stdout + ), f"Expected to find '{msg}' at level '{level}' in stdout" + else: + assert not pattern.match( + stdout + ), f"Did not expect to find '{msg}' at level '{level}' in stdout" + + +def configure_logger( + logger_name: Optional[str] = None, +) -> Tuple[logging.Logger, logging.StreamHandler[Any]]: + """Configure a logger with stdout handler""" + logger = logging.getLogger(name=logger_name) + logger.setLevel(logging.DEBUG) + logger.handlers.clear() # Clear any existing handlers + + ch = logging.StreamHandler(sys.stdout) + ch.setLevel(logging.DEBUG) + ch.setFormatter( + logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s") + ) + logger.addHandler(ch) + + return (logger, ch) + + +@pytest.fixture +def sdk_with_log_config(): # type: ignore[no-untyped-def] + """Create SDK with a log level config""" + with extended_env({"REFORGE_DATASOURCES": "LOCAL_ONLY"}): + sdk = ReforgeSDK( + Options( + x_datafile="tests/test.datafile.json", + logger_key="test.log.level", + collect_sync_interval=None, + ) + ) + + # Create a log level config: INFO by default + config = Prefab.Config( + key="test.log.level", + config_type=Prefab.ConfigType.Value("LOG_LEVEL_V2"), + rows=[ + Prefab.ConfigRow( + values=[ + Prefab.ConditionalValue( + value=Prefab.ConfigValue( + log_level=Prefab.LogLevel.Value("INFO") + ) + ) + ] + ) + ], + ) + + sdk.config_sdk().config_resolver.local_store[config.key] = {"config": config} + + yield sdk + sdk.close() + + +class TestLoggerFilter: + def test_filter_allows_logs_at_or_above_configured_level( + self, sdk_with_log_config: Any, capsys: Any + ) -> None: + """Test that LoggerFilter allows logs at or above the configured level""" + (logger, ch) = configure_logger("test.logger") + log_filter = LoggerFilter(sdk=sdk_with_log_config) + ch.addFilter(log_filter) + + # DEBUG should be filtered (below INFO) + logger.debug("Debug message") + assert_logged(capsys, "DEBUG", "Debug message", "test.logger", should_log=False) + + # INFO should pass + logger.info("Info message") + assert_logged(capsys, "INFO", "Info message", "test.logger", should_log=True) + + # WARNING should pass + logger.warning("Warning message") + assert_logged( + capsys, "WARNING", "Warning message", "test.logger", should_log=True + ) + + # ERROR should pass + logger.error("Error message") + assert_logged(capsys, "ERROR", "Error message", "test.logger", should_log=True) + + def test_filter_returns_true_when_sdk_not_available(self, capsys: Any) -> None: + """Test that filter allows all logs when SDK is not available""" + (logger, ch) = configure_logger("test.logger") + log_filter = LoggerFilter(sdk=None) # No SDK + ch.addFilter(log_filter) + + # All levels should pass when SDK not available + logger.debug("Debug message") + assert_logged(capsys, "DEBUG", "Debug message", "test.logger", should_log=True) + + logger.info("Info message") + assert_logged(capsys, "INFO", "Info message", "test.logger", should_log=True) + + def test_filter_uses_default_debug_when_config_not_found(self, capsys: Any) -> None: + """Test that filter uses DEBUG level when config not found""" + with extended_env({"REFORGE_DATASOURCES": "LOCAL_ONLY"}): + sdk = ReforgeSDK( + Options( + x_datafile="tests/test.datafile.json", + logger_key="nonexistent.key", + collect_sync_interval=None, + ) + ) + + (logger, ch) = configure_logger("test.logger") + log_filter = LoggerFilter(sdk=sdk) + ch.addFilter(log_filter) + + # With default DEBUG, all levels should pass + logger.debug("Debug message") + assert_logged( + capsys, "DEBUG", "Debug message", "test.logger", should_log=True + ) + + logger.info("Info message") + assert_logged( + capsys, "INFO", "Info message", "test.logger", should_log=True + ) + + sdk.close() + + +class TestLoggerProcessor: + def test_derive_structlog_numeric_level(self) -> None: + """Test the _derive_structlog_numeric_level helper method""" + # Test with level_number in dict (highest priority) + assert ( + LoggerProcessor._derive_structlog_numeric_level( + "warn", {"level_number": 30} + ) + == 30 + ) + + # Test with level string in dict + assert ( + LoggerProcessor._derive_structlog_numeric_level( + "warn", {"level": "warning"} + ) + == 30 + ) + + # Test with method_name + assert LoggerProcessor._derive_structlog_numeric_level("warning", {}) == 30 + + # Test warn alias + assert LoggerProcessor._derive_structlog_numeric_level("warn", {}) == 30 + + # Test debug + assert LoggerProcessor._derive_structlog_numeric_level("debug", {}) == 10 + + # Test info + assert LoggerProcessor._derive_structlog_numeric_level("info", {}) == 20 + + # Test error + assert LoggerProcessor._derive_structlog_numeric_level("error", {}) == 40 + + # Test exception alias (maps to error) + assert LoggerProcessor._derive_structlog_numeric_level("exception", {}) == 40