feat: add Aliyun SLS logging callback integration

Adds AliyunSLSLogger as a new callback integration for Alibaba Cloud
Simple Log Service (SLS), including UI form fields for region, project,
logstore, access key, and optional custom endpoint.

- New litellm/integrations/aliyun_sls.py with AliyunSLSLogger class
- Registered in custom_logger_registry, litellm_logging, and __init__.py
- Added callback_configs.json entry and UI form in callback_info_helpers.tsx
- Added aliyun-log-python-sdk==0.9.44 to requirements.txt

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
tankaikai 2026-04-10 14:57:56 +08:00
parent 0b7335201b
commit 44d92eefbb
7 changed files with 372 additions and 54 deletions

View file

@ -148,6 +148,7 @@ _custom_logger_compatible_callbacks_literal = Literal[
"vantage",
"posthog",
"levo",
"aliyun_sls",
]
cold_storage_custom_logger: Optional[_custom_logger_compatible_callbacks_literal] = None
logged_real_time_event_types: Optional[Union[List[str], Literal["*"]]] = None
@ -168,12 +169,12 @@ prometheus_latency_buckets: Optional[List[float]] = None
require_auth_for_metrics_endpoint: Optional[bool] = False
argilla_batch_size: Optional[int] = None
datadog_use_v1: Optional[bool] = False # if you want to use v1 datadog logged payload.
gcs_pub_sub_use_v1: Optional[
bool
] = False # if you want to use v1 gcs pubsub logged payload
generic_api_use_v1: Optional[
bool
] = False # if you want to use v1 generic api logged payload
gcs_pub_sub_use_v1: Optional[bool] = (
False # if you want to use v1 gcs pubsub logged payload
)
generic_api_use_v1: Optional[bool] = (
False # if you want to use v1 generic api logged payload
)
argilla_transformation_object: Optional[Dict[str, Any]] = None
_async_input_callback: List[
Union[str, Callable, "CustomLogger"]
@ -193,26 +194,26 @@ _async_failure_callback: List[
pre_call_rules: List[Callable] = []
post_call_rules: List[Callable] = []
turn_off_message_logging: Optional[bool] = False
standard_logging_payload_excluded_fields: Optional[
List[str]
] = None # Fields to exclude from StandardLoggingPayload before callbacks receive it
standard_logging_payload_excluded_fields: Optional[List[str]] = (
None # Fields to exclude from StandardLoggingPayload before callbacks receive it
)
log_raw_request_response: bool = False
redact_messages_in_exceptions: Optional[bool] = False
redact_user_api_key_info: Optional[bool] = False
filter_invalid_headers: Optional[bool] = False
add_user_information_to_llm_headers: Optional[
bool
] = None # adds user_id, team_id, token hash (params from StandardLoggingMetadata) to request headers
add_user_information_to_llm_headers: Optional[bool] = (
None # adds user_id, team_id, token hash (params from StandardLoggingMetadata) to request headers
)
store_audit_logs = False # Enterprise feature, allow users to see audit logs
skip_system_message_in_guardrail: bool = False
### end of callbacks #############
email: Optional[
str
] = None # Not used anymore, will be removed in next MAJOR release - https://github.com/BerriAI/litellm/discussions/648
token: Optional[
str
] = None # Not used anymore, will be removed in next MAJOR release - https://github.com/BerriAI/litellm/discussions/648
email: Optional[str] = (
None # Not used anymore, will be removed in next MAJOR release - https://github.com/BerriAI/litellm/discussions/648
)
token: Optional[str] = (
None # Not used anymore, will be removed in next MAJOR release - https://github.com/BerriAI/litellm/discussions/648
)
telemetry = True
max_tokens: int = DEFAULT_MAX_TOKENS # OpenAI Defaults
drop_params = bool(os.getenv("LITELLM_DROP_PARAMS", False))
@ -274,9 +275,9 @@ use_client: bool = False
ssl_verify: Union[str, bool] = True
ssl_security_level: Optional[str] = None
ssl_certificate: Optional[str] = None
ssl_ecdh_curve: Optional[
str
] = None # Set to 'X25519' to disable PQC and improve performance
ssl_ecdh_curve: Optional[str] = (
None # Set to 'X25519' to disable PQC and improve performance
)
disable_streaming_logging: bool = False
disable_token_counter: bool = False
disable_add_transform_inline_image_block: bool = False
@ -330,20 +331,24 @@ enable_loadbalancing_on_batch_endpoints: Optional[bool] = None
enable_caching_on_provider_specific_optional_params: bool = (
False # feature-flag for caching on optional params - e.g. 'top_k'
)
caching: bool = False # Not used anymore, will be removed in next MAJOR release - https://github.com/BerriAI/litellm/discussions/648
caching_with_models: bool = False # # Not used anymore, will be removed in next MAJOR release - https://github.com/BerriAI/litellm/discussions/648
cache: Optional[
"Cache"
] = None # cache object <- use this - https://docs.litellm.ai/docs/caching
caching: bool = (
False # Not used anymore, will be removed in next MAJOR release - https://github.com/BerriAI/litellm/discussions/648
)
caching_with_models: bool = (
False # # Not used anymore, will be removed in next MAJOR release - https://github.com/BerriAI/litellm/discussions/648
)
cache: Optional["Cache"] = (
None # cache object <- use this - https://docs.litellm.ai/docs/caching
)
default_in_memory_ttl: Optional[float] = None
default_redis_ttl: Optional[float] = None
default_redis_batch_cache_expiry: Optional[float] = None
model_alias_map: Dict[str, str] = {}
model_group_settings: Optional["ModelGroupSettings"] = None
max_budget: float = 0.0 # set the max budget across all providers
budget_duration: Optional[
str
] = None # proxy only - resets budget after fixed duration. You can set duration as seconds ("30s"), minutes ("30m"), hours ("30h"), days ("30d").
budget_duration: Optional[str] = (
None # proxy only - resets budget after fixed duration. You can set duration as seconds ("30s"), minutes ("30m"), hours ("30h"), days ("30d").
)
default_soft_budget: float = (
DEFAULT_SOFT_BUDGET # by default all litellm proxy keys have a soft budget of 50.0
)
@ -352,7 +357,9 @@ forward_traceparent_to_llm_provider: bool = False
_current_cost = 0.0 # private variable, used if max budget is set
error_logs: Dict = {}
add_function_to_prompt: bool = False # if function calling not supported by api, append function call details to system prompt
add_function_to_prompt: bool = (
False # if function calling not supported by api, append function call details to system prompt
)
client_session: Optional[httpx.Client] = None
aclient_session: Optional[httpx.AsyncClient] = None
model_fallbacks: Optional[List] = None # Deprecated for 'litellm.fallbacks'
@ -371,6 +378,7 @@ anthropic_beta_headers_url: str = os.getenv(
suppress_debug_info = False
dynamodb_table_name: Optional[str] = None
s3_callback_params: Optional[Dict] = None
aliyun_sls_callback_params: Optional[Dict] = None
datadog_llm_observability_params: Optional[Union[DatadogLLMObsInitParams, Dict]] = None
datadog_params: Optional[Union[DatadogInitParams, Dict]] = None
aws_sqs_callback_params: Optional[Dict] = None
@ -399,7 +407,9 @@ prometheus_emit_stream_label: bool = False
disable_add_prefix_to_prompt: bool = (
False # used by anthropic, to disable adding prefix to prompt
)
disable_copilot_system_to_assistant: bool = False # If false (default), converts all 'system' role messages to 'assistant' for GitHub Copilot compatibility. Set to true to disable this behavior.
disable_copilot_system_to_assistant: bool = (
False # If false (default), converts all 'system' role messages to 'assistant' for GitHub Copilot compatibility. Set to true to disable this behavior.
)
public_mcp_servers: Optional[List[str]] = None
public_model_groups: Optional[List[str]] = None
public_agent_groups: Optional[List[str]] = None
@ -408,9 +418,9 @@ public_agent_groups: Optional[List[str]] = None
# Old format: { "displayName": "url" } (for backward compatibility)
public_model_groups_links: Dict[str, Union[str, Dict[str, Any]]] = {}
#### REQUEST PRIORITIZATION #######
priority_reservation: Optional[
Dict[str, Union[float, "PriorityReservationDict"]]
] = None
priority_reservation: Optional[Dict[str, Union[float, "PriorityReservationDict"]]] = (
None
)
# priority_reservation_settings is lazy-loaded via __getattr__
# Only declare for type checking - at runtime __getattr__ handles it
if TYPE_CHECKING:
@ -418,13 +428,17 @@ if TYPE_CHECKING:
######## Networking Settings ########
use_aiohttp_transport: bool = True # Older variable, aiohttp is now the default. use disable_aiohttp_transport instead.
use_aiohttp_transport: bool = (
True # Older variable, aiohttp is now the default. use disable_aiohttp_transport instead.
)
aiohttp_trust_env: bool = False # set to true to use HTTP_ Proxy settings
disable_aiohttp_transport: bool = False # Set this to true to use httpx instead
disable_aiohttp_trust_env: bool = (
False # When False, aiohttp will respect HTTP(S)_PROXY env vars
)
force_ipv4: bool = False # when True, litellm will force ipv4 for all LLM requests. Some users have seen httpx ConnectionError when using ipv6.
force_ipv4: bool = (
False # when True, litellm will force ipv4 for all LLM requests. Some users have seen httpx ConnectionError when using ipv6.
)
network_mock: bool = False # When True, use mock transport — no real network calls
####### STOP SEQUENCE LIMIT #######
@ -439,13 +453,13 @@ context_window_fallbacks: Optional[List] = None
content_policy_fallbacks: Optional[List] = None
allowed_fails: int = 3
allow_dynamic_callback_disabling: bool = True
num_retries_per_request: Optional[
int
] = None # for the request overall (incl. fallbacks + model retries)
num_retries_per_request: Optional[int] = (
None # for the request overall (incl. fallbacks + model retries)
)
####### SECRET MANAGERS #####################
secret_manager_client: Optional[
Any
] = None # list of instantiated key management clients - e.g. azure kv, infisical, etc.
secret_manager_client: Optional[Any] = (
None # list of instantiated key management clients - e.g. azure kv, infisical, etc.
)
_google_kms_resource_name: Optional[str] = None
_key_management_system: Optional["KeyManagementSystem"] = None
# Note: KeyManagementSettings must be eagerly imported because _key_management_settings
@ -458,12 +472,12 @@ output_parse_pii: bool = False
from litellm.litellm_core_utils.get_model_cost_map import get_model_cost_map
model_cost = get_model_cost_map(url=model_cost_map_url)
cost_discount_config: Dict[
str, float
] = {} # Provider-specific cost discounts {"vertex_ai": 0.05} = 5% discount
cost_margin_config: Dict[
str, Union[float, Dict[str, float]]
] = {} # Provider-specific or global cost margins. Examples:
cost_discount_config: Dict[str, float] = (
{}
) # Provider-specific cost discounts {"vertex_ai": 0.05} = 5% discount
cost_margin_config: Dict[str, Union[float, Dict[str, float]]] = (
{}
) # Provider-specific or global cost margins. Examples:
# Percentage: {"openai": 0.10} = 10% margin
# Fixed: {"openai": {"fixed_amount": 0.001}} = $0.001 per request
# Global: {"global": 0.05} = 5% global margin on all providers
@ -1313,12 +1327,12 @@ from . import rag
from .types.llms.custom_llm import CustomLLMItem
custom_provider_map: List[CustomLLMItem] = []
_custom_providers: List[
str
] = [] # internal helper util, used to track names of custom providers
disable_hf_tokenizer_download: Optional[
bool
] = None # disable huggingface tokenizer download. Defaults to openai clk100
_custom_providers: List[str] = (
[]
) # internal helper util, used to track names of custom providers
disable_hf_tokenizer_download: Optional[bool] = (
None # disable huggingface tokenizer download. Defaults to openai clk100
)
global_disable_no_log_param: bool = False
### CLI UTILITIES ###

View file

@ -0,0 +1,143 @@
"""
Aliyun SLS (Simple Log Service) logging integration.
Configuration via litellm.aliyun_sls_callback_params or environment variables:
aliyun_sls_region - Region ID, e.g. cn-hangzhou
aliyun_sls_project - SLS project name
aliyun_sls_logstore - SLS logstore name
aliyun_sls_access_key_id - Alibaba Cloud AccessKey ID
aliyun_sls_access_key_secret - Alibaba Cloud AccessKey Secret
aliyun_sls_endpoint - Custom endpoint (optional), overrides region-derived endpoint
"""
import asyncio
import time
from typing import Any, Dict, Optional
from aliyun.log import LogClient, LogItem, PutLogsRequest
import litellm
from litellm._logging import verbose_logger
from litellm.integrations.custom_logger import CustomLogger
from litellm.types.utils import StandardLoggingPayload
class AliyunSLSLogger(CustomLogger):
def __init__(self) -> None:
super().__init__()
params: Dict[str, Any] = {}
if litellm.aliyun_sls_callback_params is not None:
for key, value in litellm.aliyun_sls_callback_params.items():
if isinstance(value, str) and value.startswith("os.environ/"):
params[key] = litellm.get_secret(value)
else:
params[key] = value
region = params.get("aliyun_sls_region") or ""
self.project = params.get("aliyun_sls_project") or ""
self.logstore = params.get("aliyun_sls_logstore") or ""
access_key_id = params.get("aliyun_sls_access_key_id") or ""
access_key_secret = params.get("aliyun_sls_access_key_secret") or ""
custom_endpoint = params.get("aliyun_sls_endpoint") or ""
if not region and not custom_endpoint:
raise ValueError("aliyun_sls_region is required")
if not self.project:
raise ValueError("aliyun_sls_project is required")
if not self.logstore:
raise ValueError("aliyun_sls_logstore is required")
endpoint = custom_endpoint or f"{region}.log.aliyuncs.com"
self.client = LogClient(endpoint, access_key_id, access_key_secret)
verbose_logger.debug(
"AliyunSLSLogger initialized: endpoint=%s project=%s logstore=%s",
endpoint,
self.project,
self.logstore,
)
def _put_logs(self, contents: list) -> None:
log_item = LogItem(timestamp=int(time.time()), contents=contents)
request = PutLogsRequest(
project=self.project,
logstore=self.logstore,
topic="",
source="litellm",
logitems=[log_item],
)
self.client.put_logs(request)
def _build_contents(
self,
kwargs: Dict[str, Any],
response_obj: Any,
start_time: Any,
end_time: Any,
status: str,
) -> list:
payload: Optional[StandardLoggingPayload] = kwargs.get(
"standard_logging_object"
)
if payload is None:
return [("status", status)]
contents = [
("status", status),
("model", str(payload.get("model") or "")),
("call_type", str(payload.get("call_type") or "")),
("response_id", str(payload.get("id") or "")),
("api_base", str(payload.get("api_base") or "")),
("response_time_ms", str(payload.get("response_time") or "")),
("prompt_tokens", str(payload.get("prompt_tokens") or 0)),
("completion_tokens", str(payload.get("completion_tokens") or 0)),
("total_tokens", str(payload.get("total_tokens") or 0)),
("total_cost", str(payload.get("response_cost") or 0)),
(
"user",
str(payload.get("metadata", {}).get("user_api_key_user_id") or ""),
),
(
"team_id",
str(payload.get("metadata", {}).get("user_api_key_team_id") or ""),
),
(
"key_alias",
str(payload.get("metadata", {}).get("user_api_key_alias") or ""),
),
]
error_str = payload.get("error_str")
if error_str:
contents.append(("error", str(error_str)))
return contents
async def async_log_success_event(
self, kwargs: Dict[str, Any], response_obj: Any, start_time: Any, end_time: Any
) -> None:
try:
contents = self._build_contents(
kwargs, response_obj, start_time, end_time, "success"
)
await asyncio.get_event_loop().run_in_executor(
None, self._put_logs, contents
)
except Exception as e:
verbose_logger.exception(
"AliyunSLSLogger: failed to log success event: %s", e
)
async def async_log_failure_event(
self, kwargs: Dict[str, Any], response_obj: Any, start_time: Any, end_time: Any
) -> None:
try:
contents = self._build_contents(
kwargs, response_obj, start_time, end_time, "failure"
)
await asyncio.get_event_loop().run_in_executor(
None, self._put_logs, contents
)
except Exception as e:
verbose_logger.exception(
"AliyunSLSLogger: failed to log failure event: %s", e
)

View file

@ -454,5 +454,50 @@
}
},
"description": "SQS Queue (AWS) Logging Integration"
},
{
"id": "aliyun_sls",
"displayName": "Aliyun SLS",
"logo": "aliyun.svg",
"supports_key_team_logging": false,
"dynamic_params": {
"aliyun_sls_region": {
"type": "text",
"ui_name": "Region",
"description": "Aliyun region ID, e.g. cn-hangzhou",
"required": true
},
"aliyun_sls_project": {
"type": "text",
"ui_name": "Project",
"description": "SLS project name",
"required": true
},
"aliyun_sls_logstore": {
"type": "text",
"ui_name": "Logstore",
"description": "SLS logstore name",
"required": true
},
"aliyun_sls_access_key_id": {
"type": "password",
"ui_name": "Access Key ID",
"description": "Alibaba Cloud AccessKey ID",
"required": true
},
"aliyun_sls_access_key_secret": {
"type": "password",
"ui_name": "Access Key Secret",
"description": "Alibaba Cloud AccessKey Secret",
"required": true
},
"aliyun_sls_endpoint": {
"type": "text",
"ui_name": "Endpoint (optional)",
"description": "Custom SLS endpoint, e.g. cn-hangzhou.log.aliyuncs.com. Defaults to {region}.log.aliyuncs.com",
"required": false
}
},
"description": "Aliyun SLS (Simple Log Service) Logging Integration"
}
]

View file

@ -41,6 +41,7 @@ from litellm.integrations.mlflow import MlflowLogger
from litellm.integrations.openmeter import OpenMeterLogger
from litellm.integrations.opentelemetry import OpenTelemetry
from litellm.integrations.opik.opik import OpikLogger
from litellm.integrations.aliyun_sls import AliyunSLSLogger
from litellm.integrations.posthog import PostHogLogger
from litellm.integrations.prometheus import PrometheusLogger
from litellm.integrations.s3_v2 import S3Logger
@ -102,6 +103,7 @@ class CustomLoggerRegistry:
"focus": FocusLogger,
"vantage": VantageLogger,
"posthog": PostHogLogger,
"aliyun_sls": AliyunSLSLogger,
}
try:

View file

@ -4275,6 +4275,16 @@ def _init_custom_logger_compatible_class( # noqa: PLR0915
gitlab_logger = GitLabPromptManager(gitlab_config=gitlab_config)
_in_memory_loggers.append(gitlab_logger)
return gitlab_logger # type: ignore
elif logging_integration == "aliyun_sls":
from litellm.integrations.aliyun_sls import AliyunSLSLogger
for callback in _in_memory_loggers:
if isinstance(callback, AliyunSLSLogger):
return callback
aliyun_sls_logger = AliyunSLSLogger()
_in_memory_loggers.append(aliyun_sls_logger)
return aliyun_sls_logger # type: ignore
return None
except Exception as e:
verbose_logger.exception(

89
requirements.txt Normal file
View file

@ -0,0 +1,89 @@
# LITELLM PROXY DEPENDENCIES #
# Security: explicit pins for transitive deps (CVE fixes)
urllib3==2.6.3 # CVE-2025-66471, CVE-2025-66418, CVE-2026-21441
tornado==6.5.5 # CVE-2025-67725, CVE-2025-67726, CVE-2025-67724, CVE-2026-31958, GHSA-78cv-mqj4-43f7
filelock==3.25.2 # CVE-2025-68146
h11==0.16.0 # CVE-2025-43859, GHSA-vqfr-h8mv-ghfj — HTTP request smuggling
wheel==0.46.3 # CVE-2026-24049 — path traversal
Pillow==12.1.1 #GHSA-cfh3-3jmp-rvhc
cryptography==46.0.5 #GHSA-r6ph-v2qm-q3c2
anyio==4.8.0 # openai + http req.
httpx==0.28.1
openai==2.30.0 # openai req.
fastapi==0.124.4 # server dep
starlette==0.49.1 # starlette fastapi dep
backoff==2.2.1 # server dep
pyyaml==6.0.3 # server dep
uvicorn==0.33.0 # server dep
gunicorn==23.0.0 # server dep
fastuuid==0.14.0 # for uuid4
uvloop==0.21.0 # uvicorn dep, gives us much better performance under load
boto3==1.42.80 # aws bedrock/sagemaker calls
redis==5.2.1 # redis caching
redisvl==0.4.1 ## redis semantic caching
prisma==0.11.0 # for db
nodejs-wheel-binaries==24.13.1 ## required by prisma for migrations, prevents runtime download (updated from nodejs-bin for security fixes)
mangum==0.17.0 # for aws lambda functions
pynacl==1.6.2 # for encrypting keys
google-cloud-aiplatform==1.133.0 # for vertex ai calls
google-cloud-iam==2.19.1 # for GCP IAM Redis authentication
google-genai==1.37.0
anthropic[vertex]==0.54.0
mcp==1.26.0 ; python_version >= "3.10" # for MCP server
# google-generativeai removed - deprecated, replaced by google-genai (line 21)
async_generator==1.10.0 # for async ollama calls
langfuse==2.59.7 # for langfuse self-hosted logging
prometheus_client==0.20.0 # for /metrics endpoint on proxy
aliyun-log-python-sdk==0.9.44 # for Aliyun SLS logging integration
ddtrace==2.19.0 # for advanced DD tracing / profiling
orjson==3.10.15 # fast /embedding responses
polars==1.39.3 # for data processing
apscheduler==3.11.2 # for resetting budget in background
fastapi-sso==0.16.0 # admin UI, SSO
pyjwt[crypto]==2.12.1 ; python_version >= "3.9"
python-multipart==0.0.20 # admin UI
jaraco.context==6.1.2
azure-ai-contentsafety==1.0.0 # for azure content safety
azure-identity==1.25.3 ; python_version >= "3.9" # for azure content safety
azure-keyvault==4.2.0 # for azure KMS integration
azure-storage-file-datalake==12.20.0 # for azure buck storage logging
opentelemetry-api==1.28.0
opentelemetry-sdk==1.28.0
opentelemetry-exporter-otlp==1.28.0
a2a-sdk==0.3.25 ; python_version >= "3.10"
# grpcio: pinned to 1.80.0 (past reconnect bug #38290 in 1.68.x, has Python 3.14 wheels)
grpcio==1.80.0
sentry_sdk==2.21.0 # for sentry error handling
detect-secrets==1.5.0 # Enterprise - secret detection / masking in LLM requests
tzdata==2025.1 # IANA time zone database
litellm-proxy-extras==0.4.65 # for proxy extras - e.g. prisma migrations
llm-sandbox==0.3.31 # for skill execution in sandbox
### LITELLM PACKAGE DEPENDENCIES
python-dotenv==1.0.1 # for env
tiktoken==0.12.0 # for calculating usage
importlib-metadata==8.5.0 # for random utils
tokenizers==0.22.2 # for calculating usage
click==8.1.8 # for proxy cli
rich==13.9.4 # for litellm proxy cli
jinja2==3.1.6 # for prompt templates
aiohttp==3.13.5 # for network calls
tenacity==8.5.0 # for retrying requests, when litellm.num_retries set
pydantic==2.12.5 # proxy + openai req. + mcp
jsonschema==4.23.0 # validating json schema - aligned with openapi-core + mcp
websockets==15.0.1 # for realtime API
soundfile==0.12.1 # for audio file processing
openapi-core==0.21.0 # for OpenAPI compliance tests
pypdf==6.9.2 # for PDF text extraction in RAG ingestion (CVE-2026-27888)
# Transitive deps pinned to prevent floating between builds
aiofiles==24.1.0 # transitive dep (langfuse)
colorlog==6.10.1 # transitive dep (ddtrace)
grpc-google-iam-v1==0.14.3 # transitive dep (google-cloud-iam)
hf-xet==1.4.2 # transitive dep (huggingface_hub)
requests-toolbelt==1.0.0 # transitive dep (langfuse)
########################
# LITELLM ENTERPRISE DEPENDENCIES
########################
litellm-enterprise==0.1.37

View file

@ -150,6 +150,21 @@ export const CALLBACK_CONFIGS: CallbackConfig[] = [
},
description: "SQS Queue (AWS) Logging Integration",
},
{
id: "aliyun_sls",
displayName: "Aliyun SLS",
logo: `${asset_logos_folder}aliyun.svg`,
supports_key_team_logging: false,
dynamic_params: {
aliyun_sls_region: "text",
aliyun_sls_project: "text",
aliyun_sls_logstore: "text",
aliyun_sls_access_key_id: "password",
aliyun_sls_access_key_secret: "password",
aliyun_sls_endpoint: "text",
},
description: "Aliyun SLS (Simple Log Service) Logging Integration",
},
];
// Create callbackInfo object mapping display names to config objects