Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(backend): 添加 APM #1894 #1895

Merged
merged 1 commit into from
Nov 18, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 3 additions & 6 deletions dbm-ui/backend/env/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,10 @@
an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
specific language governing permissions and limitations under the License.
"""
import base64
import json
from collections import defaultdict
from typing import Dict, List

from .apigw_domains import *
from .bkrepo import *
from .apigw_domains import * # pylint: disable=wildcard-import
from .apm import * # pylint: disable=wildcard-import
from .bkrepo import * # pylint: disable=wildcard-import

APP_CODE = get_type_env(key="APP_ID", default="bk-dbm", _type=str)
SECRET_KEY = get_type_env(key="APP_TOKEN", default="yb2gur=g)hxbmpk3#b%ez5_#6o!tf9vkqsnwo4dxyr0n&w3=9k", _type=str)
Expand Down
14 changes: 14 additions & 0 deletions dbm-ui/backend/env/apm.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# -*- coding: utf-8 -*-
"""
TencentBlueKing is pleased to support the open source community by making 蓝鲸智云-DB管理系统(BlueKing-BK-DBM) available.
Copyright (C) 2017-2023 THL A29 Limited, a Tencent company. All rights reserved.
Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License.
You may obtain a copy of the License at https://opensource.org/licenses/MIT
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
specific language governing permissions and limitations under the License.
"""
from backend.utils.env import get_type_env

ENABLE_OTEL_TRACE = get_type_env(key="ENABLE_OTEL_TRACE", _type=bool)
BK_APP_OTEL_INSTRUMENT_DB_API = get_type_env(key="BK_APP_OTEL_INSTRUMENT_DB_API", _type=bool)
11 changes: 11 additions & 0 deletions dbm-ui/blue_krill/data_types/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# -*- coding: utf-8 -*-
"""
* TencentBlueKing is pleased to support the open source community by making 蓝鲸智云-蓝鲸 PaaS 平台(BlueKing-PaaS) available.
* Copyright (C) 2017-2021 THL A29 Limited, a Tencent company. All rights reserved.
* Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at http://opensource.org/licenses/MIT
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
"""
# 因为 peotry 包兼容问题,临时把此包放到项目中,并且 blue_krill 只使用了 data_types, 后续考虑移除
229 changes: 229 additions & 0 deletions dbm-ui/blue_krill/data_types/enum.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,229 @@
# -*- coding: utf-8 -*-
"""
* TencentBlueKing is pleased to support the open source community by making 蓝鲸智云-蓝鲸 PaaS 平台(BlueKing-PaaS) available.
* Copyright (C) 2017-2021 THL A29 Limited, a Tencent company. All rights reserved.
* Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at http://opensource.org/licenses/MIT
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
"""
import dataclasses
from collections import OrderedDict
from enum import Enum as OrigEnum
from enum import EnumMeta, auto
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple, Type


@dataclasses.dataclass(init=False)
class FeatureFlagField:
label: str
default: bool
name: str

def __init__(
self,
name: Optional[str] = None,
label: Optional[str] = None,
default: bool = False,
):
"""FeatureFlag 中的字段, 记录了 label、default 等属性

:param label: 对这个 feature flag 的描述语句
:param default: feature flag 的默认状态, 对于新引入的 feature flag, 该值建议为 False.
:param name: 当前 Feature Flag 的名字, 当使用 register_ext_feature_flag 进行注册时, 必须提供该字段.
"""
self.name = name or ""
self.label = label or name or ""
self.default = default

def __set_name__(self, owner, name):
"""利用描述符协议, 往 FeatureFlagField 注入 FeatureFlag 的名字."""
self.name = name
if not self.label:
self.label = self.name

def __get__(self, instance, owner):
"""返回 FeatureFlag 的名称. 因为 FeatureFlag 的值就是他自身的名称."""
return self.name

def __str__(self):
return self.name


class FeatureFlagMeta(type):
_feature_flag_fields_: Dict[str, FeatureFlagField]

def __new__(mcs, cls_name: str, bases, dct: Dict):
_feature_flag_fields_ = {}
for base in bases:
_feature_flag_fields_.update(getattr(base, "_feature_flag_fields_", {}))

for attr, field in dct.items():
if not isinstance(field, FeatureFlagField):
continue

if _feature_flag_fields_.get(attr, field).label != field.label:
raise ValueError("Two Feature Flags cannot be set to the same value.")
if not attr:
raise ValueError("Feature flag's name can not be empty.")

_feature_flag_fields_[attr] = field

dct["_feature_flag_fields_"] = _feature_flag_fields_
return super().__new__(mcs, cls_name, bases, dct)

def _get_feature_fields_(cls) -> Dict[str, FeatureFlagField]:
return cls._feature_flag_fields_

def __iter__(cls):
for feature in cls._get_feature_fields_():
yield feature


class FeatureFlag(str, metaclass=FeatureFlagMeta):
def __new__(cls, value):
"""Cast a string into a predefined feature flag."""
for field in cls._get_feature_fields_().values():
if field.name == value:
return value
return cls._missing_(value)

@classmethod
def _missing_(cls, value) -> str:
raise ValueError("%r is not a valid %s" % (value, cls.__name__))

@classmethod
def get_default_flags(cls) -> Dict[str, bool]:
"""Get the default user feature flags, client is safe to modify the result because it's a copy"""
features = {field.name: field.default for field in cls._get_feature_fields_().values()}
return features.copy()

@classmethod
def get_django_choices(cls) -> List[Tuple[str, str]]:
"""Get Django-Like Choices for this FeatureFlag Collection."""
return [(field.name, field.label) for field in cls._get_feature_fields_().values()]

@classmethod
def get_feature_label(cls, feature: str) -> str:
"""Get the label of provided feature flag"""
return cls._get_feature_fields_()[cls(feature)].label

@classmethod
def register_feature_flag(cls, field: FeatureFlagField):
"""注册额外的FeatureFlagField"""
name = field.name
if cls._feature_flag_fields_.get(name, field).label != field.label:
raise ValueError("Two Feature Flags cannot be set to the same value.")
if not name:
raise ValueError("Feature flag's name can not be empty.")

cls._feature_flag_fields_[name] = field
setattr(cls, name, field)


# Treat `EnumField` as Any to make it pass some strict type checking, for example:
# when `foo = EnumField(...)` and `foo` was read via `SomeClass.foo.value`.
#
# TODO: Write a mypy plugin to provide better typing experience.
if TYPE_CHECKING:
EnumFieldBase = Any
else:
EnumFieldBase = object


class EnumField(EnumFieldBase):
"""Use it with `StructuredEnum` type

:param real_value: the real value of enum member
:param label: the label text of current enum value
:param is_reserved: if current member was reserved, it will not be included in choices
"""

def __init__(self, real_value: Any, label: Optional[str] = None, is_reserved: bool = False):
self.real_value = real_value
self.label = label
self.is_reserved = is_reserved

def set_label_if_empty(self, key: str):
"""Set field's label if not provided"""
if not self.label:
self.label = key.lower().replace("_", " ").capitalize()


class StructuredEnumMeta(EnumMeta):
"""The metaclass of StructuredEnum"""

__field_members__: Dict[Type, Dict]

def __new__(metacls, cls, bases, classdict):
field_members = metacls.process_enum_fields(classdict)
classdict["__field_members__"] = field_members
return super().__new__(metacls, cls, bases, classdict)

@staticmethod
def process_enum_fields(classdict) -> Dict:
"""Iterate all enum members, transform them into `EnumField` objects and return as a dict.

`EnumField` members in `classdict` will be replaced with their `real_value` attribute so `EnumMeta` can
continue the initialization.
"""
fields = OrderedDict()
# Find out all `EnumField` instance, store them into class so we can use them later
for key, member in classdict.items():
# Ignore all private members
if key.startswith("_"):
continue

# Turn regular enum member into EnumField instance
if not isinstance(member, EnumField) and isinstance(member, (int, str, auto)):
member = EnumField(member)
if not isinstance(member, EnumField) or member.is_reserved:
continue

member.set_label_if_empty(key)
fields[key] = member
# Use dict's setitem method because setting value with `classdict[key]` is forbidden
dict.__setitem__(classdict, key, member.real_value)
return fields

def get_field_members(cls) -> Dict:
return cls.__field_members__


class StructuredEnum(OrigEnum, metaclass=StructuredEnumMeta):
"""Structured Enum type, providing extra features such as getting enum members as choices tuple"""

@classmethod
def get_django_choices(cls) -> List[Tuple[Any, str]]:
"""Get Django-Like Choices for all field members."""
return cls.get_choices()

@classmethod
def get_choice_label(cls, value: Any) -> str:
"""Get the label of field member by value"""
if isinstance(value, cls):
value = value.value

members = cls.get_field_members()
for field in members.values():
if value == field.real_value:
return field.label

return value

@classmethod
def get_labels(cls) -> List[str]:
"""Get the label list for all field members."""
return [item[1] for item in cls.get_choices()]

@classmethod
def get_values(cls) -> List[Any]:
"""Get the value list for all field members."""
return [item[0] for item in cls.get_choices()]

@classmethod
def get_choices(cls) -> List[Tuple[Any, str]]:
"""Get Choices for all field members."""
members = cls.get_field_members()
return [(field.real_value, field.label) for field in members.values()]
7 changes: 7 additions & 0 deletions dbm-ui/config/default.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,8 @@
"iam.contrib.iam_migration",
# bk-audit
"bk_audit.contrib.bk_audit",
# apm
"blueapps.opentelemetry.instrument_app",
# backend
"backend.core.storages",
"backend.core.encrypt",
Expand Down Expand Up @@ -232,6 +234,11 @@
"formatter": "bk_audit.contrib.django.formatters.DjangoFormatter",
}

# apm 配置
ENABLE_OTEL_TRACE = env.ENABLE_OTEL_TRACE
BK_APP_OTEL_INSTRUMENT_DB_API = env.BK_APP_OTEL_INSTRUMENT_DB_API # 是否开启 DB 访问 trace(开启后 span 数量会明显增多)


# BAMBOO PIPELINE 配置
AUTO_UPDATE_COMPONENT_MODELS = False

Expand Down
Loading
Loading