Compare commits

..

23 Commits

Author SHA1 Message Date
nityanandagohain
dc63dc0b28 fix: integration with opamp spanmapping 2026-04-24 19:25:32 +05:30
nityanandagohain
fbb47f7fb9 Merge remote-tracking branch 'origin/main' into issue_4361 2026-04-24 11:46:32 +05:30
nityanandagohain
7aa69a3265 fix: use mustnewuuid 2026-04-24 00:16:29 +05:30
nityanandagohain
6de9ebfcac fix: address comments 2026-04-23 22:14:06 +05:30
nityanandagohain
85c7ce0680 fix: address comments 2026-04-23 18:46:32 +05:30
nityanandagohain
910cbf55cf Merge remote-tracking branch 'origin/main' into issue_4361 2026-04-23 16:24:32 +05:30
nityanandagohain
c30b3870f2 fix: address comments 2026-04-23 16:17:36 +05:30
nityanandagohain
a4da71eb52 fix: more changes 2026-04-22 14:05:56 +05:30
nityanandagohain
f5ed4c90b0 fix: address more comments 2026-04-22 13:54:27 +05:30
nityanandagohain
c03e97221a fix: address comments 2026-04-21 23:31:54 +05:30
nityanandagohain
cd224616f0 fix: address comments 2026-04-21 23:19:49 +05:30
nityanandagohain
50b42aca8c fix: lint issues 2026-04-20 16:09:50 +05:30
nityanandagohain
dbad6cff96 Merge remote-tracking branch 'origin/issue_4361' into issue_4361 2026-04-20 16:09:35 +05:30
nityanandagohain
73a44cee77 Merge remote-tracking branch 'origin/main' into issue_4361 2026-04-20 16:09:12 +05:30
nityanandagohain
b9b7c10af9 fix: remove lint issues 2026-04-20 16:09:09 +05:30
Nityananda Gohain
3d71707f0b Merge branch 'main' into issue_4361 2026-04-20 11:02:57 +05:30
nityanandagohain
f59f611ea2 fix: remove stutters 2026-04-20 11:00:38 +05:30
nityanandagohain
07131f10a9 fix: minor changes 2026-04-20 10:52:17 +05:30
nityanandagohain
d930e77870 fix: cleanup 2026-04-20 10:40:36 +05:30
nityanandagohain
8a9f6bee98 Merge remote-tracking branch 'origin/main' into issue_4361 2026-04-20 09:14:50 +05:30
nityanandagohain
4b06b4546f Merge remote-tracking branch 'origin/issue_4361' into issue_4361 2026-04-12 22:02:12 +05:30
nityanandagohain
526b08a179 feat: 1.types and handler for ai-o11y attribute mapping 2026-04-12 17:32:30 +05:30
nityanandagohain
c7ab610bd8 feat: 1.types and handler for ai-o11y attribute mapping 2026-04-12 17:31:40 +05:30
30 changed files with 3368 additions and 1479 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -12,6 +12,7 @@ import (
"github.com/SigNoz/signoz/pkg/cache/memorycache"
"github.com/SigNoz/signoz/pkg/errors"
"github.com/SigNoz/signoz/pkg/modules/spanmapper/implspanmapper"
"github.com/gorilla/handlers"
@@ -111,9 +112,11 @@ func NewServer(config signoz.Config, signoz *signoz.SigNoz) (*Server, error) {
}
// initiate agent config handler
spanAttrMappingFeature := implspanmapper.NewSpanAttrMappingFeature(signoz.Modules.SpanMapper)
agentConfMgr, err := agentConf.Initiate(&agentConf.ManagerOptions{
Store: signoz.SQLStore,
AgentFeatures: []agentConf.AgentFeature{logParsingPipelineController},
AgentFeatures: []agentConf.AgentFeature{logParsingPipelineController, spanAttrMappingFeature},
})
if err != nil {
return nil, err

View File

@@ -1,398 +0,0 @@
/**
* ! Do not edit manually
* * The file has been auto-generated using Orval for SigNoz
* * regenerate with 'yarn generate:api'
* SigNoz
*/
import { useMutation, useQuery } from 'react-query';
import type {
InvalidateOptions,
MutationFunction,
QueryClient,
QueryFunction,
QueryKey,
UseMutationOptions,
UseMutationResult,
UseQueryOptions,
UseQueryResult,
} from 'react-query';
import type {
DeleteLLMPricingRulePathParameters,
GetLLMPricingRule200,
GetLLMPricingRulePathParameters,
ListLLMPricingRules200,
ListLLMPricingRulesParams,
LlmpricingruletypesUpdatableLLMPricingRulesDTO,
RenderErrorResponseDTO,
} from '../sigNoz.schemas';
import { GeneratedAPIInstance } from '../../../generatedAPIInstance';
import type { ErrorType, BodyType } from '../../../generatedAPIInstance';
/**
* Returns all LLM pricing rules for the authenticated org, with pagination.
* @summary List pricing rules
*/
export const listLLMPricingRules = (
params?: ListLLMPricingRulesParams,
signal?: AbortSignal,
) => {
return GeneratedAPIInstance<ListLLMPricingRules200>({
url: `/api/v1/llm_pricing_rules`,
method: 'GET',
params,
signal,
});
};
export const getListLLMPricingRulesQueryKey = (
params?: ListLLMPricingRulesParams,
) => {
return [`/api/v1/llm_pricing_rules`, ...(params ? [params] : [])] as const;
};
export const getListLLMPricingRulesQueryOptions = <
TData = Awaited<ReturnType<typeof listLLMPricingRules>>,
TError = ErrorType<RenderErrorResponseDTO>,
>(
params?: ListLLMPricingRulesParams,
options?: {
query?: UseQueryOptions<
Awaited<ReturnType<typeof listLLMPricingRules>>,
TError,
TData
>;
},
) => {
const { query: queryOptions } = options ?? {};
const queryKey =
queryOptions?.queryKey ?? getListLLMPricingRulesQueryKey(params);
const queryFn: QueryFunction<
Awaited<ReturnType<typeof listLLMPricingRules>>
> = ({ signal }) => listLLMPricingRules(params, signal);
return { queryKey, queryFn, ...queryOptions } as UseQueryOptions<
Awaited<ReturnType<typeof listLLMPricingRules>>,
TError,
TData
> & { queryKey: QueryKey };
};
export type ListLLMPricingRulesQueryResult = NonNullable<
Awaited<ReturnType<typeof listLLMPricingRules>>
>;
export type ListLLMPricingRulesQueryError = ErrorType<RenderErrorResponseDTO>;
/**
* @summary List pricing rules
*/
export function useListLLMPricingRules<
TData = Awaited<ReturnType<typeof listLLMPricingRules>>,
TError = ErrorType<RenderErrorResponseDTO>,
>(
params?: ListLLMPricingRulesParams,
options?: {
query?: UseQueryOptions<
Awaited<ReturnType<typeof listLLMPricingRules>>,
TError,
TData
>;
},
): UseQueryResult<TData, TError> & { queryKey: QueryKey } {
const queryOptions = getListLLMPricingRulesQueryOptions(params, options);
const query = useQuery(queryOptions) as UseQueryResult<TData, TError> & {
queryKey: QueryKey;
};
query.queryKey = queryOptions.queryKey;
return query;
}
/**
* @summary List pricing rules
*/
export const invalidateListLLMPricingRules = async (
queryClient: QueryClient,
params?: ListLLMPricingRulesParams,
options?: InvalidateOptions,
): Promise<QueryClient> => {
await queryClient.invalidateQueries(
{ queryKey: getListLLMPricingRulesQueryKey(params) },
options,
);
return queryClient;
};
/**
* Single write endpoint used by both the user and the Zeus sync job. Per-rule match is by id, then sourceId, then insert. Override rows (is_override=true) are fully preserved when the request does not provide isOverride; only synced_at is stamped.
* @summary Bulk update pricing rules
*/
export const updateLLMPricingRules = (
llmpricingruletypesUpdatableLLMPricingRulesDTO: BodyType<LlmpricingruletypesUpdatableLLMPricingRulesDTO>,
) => {
return GeneratedAPIInstance<void>({
url: `/api/v1/llm_pricing_rules`,
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
data: llmpricingruletypesUpdatableLLMPricingRulesDTO,
});
};
export const getUpdateLLMPricingRulesMutationOptions = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown,
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof updateLLMPricingRules>>,
TError,
{ data: BodyType<LlmpricingruletypesUpdatableLLMPricingRulesDTO> },
TContext
>;
}): UseMutationOptions<
Awaited<ReturnType<typeof updateLLMPricingRules>>,
TError,
{ data: BodyType<LlmpricingruletypesUpdatableLLMPricingRulesDTO> },
TContext
> => {
const mutationKey = ['updateLLMPricingRules'];
const { mutation: mutationOptions } = options
? options.mutation &&
'mutationKey' in options.mutation &&
options.mutation.mutationKey
? options
: { ...options, mutation: { ...options.mutation, mutationKey } }
: { mutation: { mutationKey } };
const mutationFn: MutationFunction<
Awaited<ReturnType<typeof updateLLMPricingRules>>,
{ data: BodyType<LlmpricingruletypesUpdatableLLMPricingRulesDTO> }
> = (props) => {
const { data } = props ?? {};
return updateLLMPricingRules(data);
};
return { mutationFn, ...mutationOptions };
};
export type UpdateLLMPricingRulesMutationResult = NonNullable<
Awaited<ReturnType<typeof updateLLMPricingRules>>
>;
export type UpdateLLMPricingRulesMutationBody =
BodyType<LlmpricingruletypesUpdatableLLMPricingRulesDTO>;
export type UpdateLLMPricingRulesMutationError =
ErrorType<RenderErrorResponseDTO>;
/**
* @summary Bulk update pricing rules
*/
export const useUpdateLLMPricingRules = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown,
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof updateLLMPricingRules>>,
TError,
{ data: BodyType<LlmpricingruletypesUpdatableLLMPricingRulesDTO> },
TContext
>;
}): UseMutationResult<
Awaited<ReturnType<typeof updateLLMPricingRules>>,
TError,
{ data: BodyType<LlmpricingruletypesUpdatableLLMPricingRulesDTO> },
TContext
> => {
const mutationOptions = getUpdateLLMPricingRulesMutationOptions(options);
return useMutation(mutationOptions);
};
/**
* Hard-deletes a pricing rule. If auto-synced, it will be recreated on the next sync cycle.
* @summary Delete a pricing rule
*/
export const deleteLLMPricingRule = ({
id,
}: DeleteLLMPricingRulePathParameters) => {
return GeneratedAPIInstance<void>({
url: `/api/v1/llm_pricing_rules/${id}`,
method: 'DELETE',
});
};
export const getDeleteLLMPricingRuleMutationOptions = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown,
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof deleteLLMPricingRule>>,
TError,
{ pathParams: DeleteLLMPricingRulePathParameters },
TContext
>;
}): UseMutationOptions<
Awaited<ReturnType<typeof deleteLLMPricingRule>>,
TError,
{ pathParams: DeleteLLMPricingRulePathParameters },
TContext
> => {
const mutationKey = ['deleteLLMPricingRule'];
const { mutation: mutationOptions } = options
? options.mutation &&
'mutationKey' in options.mutation &&
options.mutation.mutationKey
? options
: { ...options, mutation: { ...options.mutation, mutationKey } }
: { mutation: { mutationKey } };
const mutationFn: MutationFunction<
Awaited<ReturnType<typeof deleteLLMPricingRule>>,
{ pathParams: DeleteLLMPricingRulePathParameters }
> = (props) => {
const { pathParams } = props ?? {};
return deleteLLMPricingRule(pathParams);
};
return { mutationFn, ...mutationOptions };
};
export type DeleteLLMPricingRuleMutationResult = NonNullable<
Awaited<ReturnType<typeof deleteLLMPricingRule>>
>;
export type DeleteLLMPricingRuleMutationError =
ErrorType<RenderErrorResponseDTO>;
/**
* @summary Delete a pricing rule
*/
export const useDeleteLLMPricingRule = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown,
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof deleteLLMPricingRule>>,
TError,
{ pathParams: DeleteLLMPricingRulePathParameters },
TContext
>;
}): UseMutationResult<
Awaited<ReturnType<typeof deleteLLMPricingRule>>,
TError,
{ pathParams: DeleteLLMPricingRulePathParameters },
TContext
> => {
const mutationOptions = getDeleteLLMPricingRuleMutationOptions(options);
return useMutation(mutationOptions);
};
/**
* Returns a single LLM pricing rule by ID.
* @summary Get a pricing rule
*/
export const getLLMPricingRule = (
{ id }: GetLLMPricingRulePathParameters,
signal?: AbortSignal,
) => {
return GeneratedAPIInstance<GetLLMPricingRule200>({
url: `/api/v1/llm_pricing_rules/${id}`,
method: 'GET',
signal,
});
};
export const getGetLLMPricingRuleQueryKey = ({
id,
}: GetLLMPricingRulePathParameters) => {
return [`/api/v1/llm_pricing_rules/${id}`] as const;
};
export const getGetLLMPricingRuleQueryOptions = <
TData = Awaited<ReturnType<typeof getLLMPricingRule>>,
TError = ErrorType<RenderErrorResponseDTO>,
>(
{ id }: GetLLMPricingRulePathParameters,
options?: {
query?: UseQueryOptions<
Awaited<ReturnType<typeof getLLMPricingRule>>,
TError,
TData
>;
},
) => {
const { query: queryOptions } = options ?? {};
const queryKey =
queryOptions?.queryKey ?? getGetLLMPricingRuleQueryKey({ id });
const queryFn: QueryFunction<
Awaited<ReturnType<typeof getLLMPricingRule>>
> = ({ signal }) => getLLMPricingRule({ id }, signal);
return {
queryKey,
queryFn,
enabled: !!id,
...queryOptions,
} as UseQueryOptions<
Awaited<ReturnType<typeof getLLMPricingRule>>,
TError,
TData
> & { queryKey: QueryKey };
};
export type GetLLMPricingRuleQueryResult = NonNullable<
Awaited<ReturnType<typeof getLLMPricingRule>>
>;
export type GetLLMPricingRuleQueryError = ErrorType<RenderErrorResponseDTO>;
/**
* @summary Get a pricing rule
*/
export function useGetLLMPricingRule<
TData = Awaited<ReturnType<typeof getLLMPricingRule>>,
TError = ErrorType<RenderErrorResponseDTO>,
>(
{ id }: GetLLMPricingRulePathParameters,
options?: {
query?: UseQueryOptions<
Awaited<ReturnType<typeof getLLMPricingRule>>,
TError,
TData
>;
},
): UseQueryResult<TData, TError> & { queryKey: QueryKey } {
const queryOptions = getGetLLMPricingRuleQueryOptions({ id }, options);
const query = useQuery(queryOptions) as UseQueryResult<TData, TError> & {
queryKey: QueryKey;
};
query.queryKey = queryOptions.queryKey;
return query;
}
/**
* @summary Get a pricing rule
*/
export const invalidateGetLLMPricingRule = async (
queryClient: QueryClient,
{ id }: GetLLMPricingRulePathParameters,
options?: InvalidateOptions,
): Promise<QueryClient> => {
await queryClient.invalidateQueries(
{ queryKey: getGetLLMPricingRuleQueryKey({ id }) },
options,
);
return queryClient;
};

View File

@@ -3278,173 +3278,6 @@ export enum InframonitoringtypesResponseTypeDTO {
list = 'list',
grouped_list = 'grouped_list',
}
export interface LlmpricingruletypesGettablePricingRulesDTO {
/**
* @type array
* @nullable true
*/
items: LlmpricingruletypesLLMPricingRuleDTO[] | null;
/**
* @type integer
*/
limit: number;
/**
* @type integer
*/
offset: number;
/**
* @type integer
*/
total: number;
}
export interface LlmpricingruletypesLLMPricingRuleDTO {
cacheMode: LlmpricingruletypesLLMPricingRuleCacheModeDTO;
/**
* @type number
* @format double
*/
costCacheRead: number;
/**
* @type number
* @format double
*/
costCacheWrite: number;
/**
* @type number
* @format double
*/
costInput: number;
/**
* @type number
* @format double
*/
costOutput: number;
/**
* @type string
* @format date-time
*/
createdAt?: Date;
/**
* @type string
*/
createdBy?: string;
/**
* @type boolean
*/
enabled: boolean;
/**
* @type string
*/
id: string;
/**
* @type boolean
*/
isOverride: boolean;
/**
* @type string
*/
modelName: string;
/**
* @type array
* @nullable true
*/
modelPattern: string[] | null;
/**
* @type string
*/
orgId: string;
/**
* @type string
*/
sourceId?: string;
/**
* @type string
* @format date-time
* @nullable true
*/
syncedAt?: Date | null;
unit: LlmpricingruletypesLLMPricingRuleUnitDTO;
/**
* @type string
* @format date-time
*/
updatedAt?: Date;
/**
* @type string
*/
updatedBy?: string;
}
export enum LlmpricingruletypesLLMPricingRuleCacheModeDTO {
subtract = 'subtract',
additive = 'additive',
unknown = 'unknown',
}
export enum LlmpricingruletypesLLMPricingRuleUnitDTO {
per_million_tokens = 'per_million_tokens',
}
export interface LlmpricingruletypesUpdatableLLMPricingRuleDTO {
cacheMode: LlmpricingruletypesLLMPricingRuleCacheModeDTO;
/**
* @type number
* @format double
*/
costCacheRead: number;
/**
* @type number
* @format double
*/
costCacheWrite: number;
/**
* @type number
* @format double
*/
costInput: number;
/**
* @type number
* @format double
*/
costOutput: number;
/**
* @type boolean
*/
enabled: boolean;
/**
* @type string
* @nullable true
*/
id?: string | null;
/**
* @type boolean
* @nullable true
*/
isOverride?: boolean | null;
/**
* @type string
*/
modelName: string;
/**
* @type array
* @nullable true
*/
modelPattern: string[] | null;
/**
* @type string
* @nullable true
*/
sourceId?: string | null;
unit: LlmpricingruletypesLLMPricingRuleUnitDTO;
}
export interface LlmpricingruletypesUpdatableLLMPricingRulesDTO {
/**
* @type array
* @nullable true
*/
rules: LlmpricingruletypesUpdatableLLMPricingRuleDTO[] | null;
}
export interface MetricsexplorertypesInspectMetricsRequestDTO {
/**
* @type integer
@@ -5637,6 +5470,187 @@ export interface Sigv4SigV4ConfigDTO {
[key: string]: unknown;
}
export enum SpantypesFieldContextDTO {
attribute = 'attribute',
resource = 'resource',
}
export interface SpantypesGettableSpanMapperGroupsDTO {
/**
* @type array
*/
items: SpantypesSpanMapperGroupDTO[];
}
export interface SpantypesPostableSpanMapperDTO {
config: SpantypesSpanMapperConfigDTO;
/**
* @type boolean
*/
enabled?: boolean;
field_context: SpantypesFieldContextDTO;
/**
* @type string
*/
name: string;
}
export interface SpantypesPostableSpanMapperGroupDTO {
category: SpantypesSpanMapperGroupCategoryDTO;
condition: SpantypesSpanMapperGroupConditionDTO;
/**
* @type boolean
*/
enabled?: boolean;
/**
* @type string
*/
name: string;
}
export interface SpantypesSpanMapperDTO {
config: SpantypesSpanMapperConfigDTO;
/**
* @type string
* @format date-time
*/
createdAt?: Date;
/**
* @type string
*/
createdBy?: string;
/**
* @type boolean
*/
enabled: boolean;
field_context: SpantypesFieldContextDTO;
/**
* @type string
*/
group_id: string;
/**
* @type string
*/
id: string;
/**
* @type string
*/
name: string;
/**
* @type string
* @format date-time
*/
updatedAt?: Date;
/**
* @type string
*/
updatedBy?: string;
}
export interface SpantypesSpanMapperConfigDTO {
/**
* @type array
* @nullable true
*/
sources: SpantypesSpanMapperSourceDTO[] | null;
}
export interface SpantypesSpanMapperGroupDTO {
category: SpantypesSpanMapperGroupCategoryDTO;
condition: SpantypesSpanMapperGroupConditionDTO;
/**
* @type string
* @format date-time
*/
createdAt?: Date;
/**
* @type string
*/
createdBy?: string;
/**
* @type boolean
*/
enabled: boolean;
/**
* @type string
*/
id: string;
/**
* @type string
*/
name: string;
/**
* @type string
*/
orgId: string;
/**
* @type string
* @format date-time
*/
updatedAt?: Date;
/**
* @type string
*/
updatedBy?: string;
}
export interface SpantypesSpanMapperGroupCategoryDTO {
[key: string]: unknown;
}
export interface SpantypesSpanMapperGroupConditionDTO {
/**
* @type array
* @nullable true
*/
attributes: string[] | null;
/**
* @type array
* @nullable true
*/
resource: string[] | null;
}
export enum SpantypesSpanMapperOperationDTO {
move = 'move',
copy = 'copy',
}
export interface SpantypesSpanMapperSourceDTO {
context: SpantypesFieldContextDTO;
/**
* @type string
*/
key: string;
operation: SpantypesSpanMapperOperationDTO;
/**
* @type integer
*/
priority: number;
}
export interface SpantypesUpdatableSpanMapperDTO {
config?: SpantypesSpanMapperConfigDTO;
/**
* @type boolean
* @nullable true
*/
enabled?: boolean | null;
field_context?: SpantypesFieldContextDTO;
}
export interface SpantypesUpdatableSpanMapperGroupDTO {
condition?: SpantypesSpanMapperGroupConditionDTO;
/**
* @type boolean
* @nullable true
*/
enabled?: boolean | null;
/**
* @type string
* @nullable true
*/
name?: string | null;
}
export enum TelemetrytypesFieldContextDTO {
metric = 'metric',
log = 'log',
@@ -6821,41 +6835,6 @@ export type CreateInvite201 = {
status: string;
};
export type ListLLMPricingRulesParams = {
/**
* @type integer
* @description undefined
*/
offset?: number;
/**
* @type integer
* @description undefined
*/
limit?: number;
};
export type ListLLMPricingRules200 = {
data: LlmpricingruletypesGettablePricingRulesDTO;
/**
* @type string
*/
status: string;
};
export type DeleteLLMPricingRulePathParameters = {
id: string;
};
export type GetLLMPricingRulePathParameters = {
id: string;
};
export type GetLLMPricingRule200 = {
data: LlmpricingruletypesLLMPricingRuleDTO;
/**
* @type string
*/
status: string;
};
export type ListPromotedAndIndexedPaths200 = {
/**
* @type array
@@ -7122,6 +7101,71 @@ export type GetMyServiceAccount200 = {
status: string;
};
export type ListSpanMapperGroupsParams = {
/**
* @description undefined
*/
category?: SpantypesSpanMapperGroupCategoryDTO;
/**
* @type boolean
* @nullable true
* @description undefined
*/
enabled?: boolean | null;
};
export type ListSpanMapperGroups200 = {
data: SpantypesGettableSpanMapperGroupsDTO;
/**
* @type string
*/
status: string;
};
export type CreateSpanMapperGroup201 = {
data: SpantypesSpanMapperGroupDTO;
/**
* @type string
*/
status: string;
};
export type DeleteSpanMapperGroupPathParameters = {
groupId: string;
};
export type UpdateSpanMapperGroupPathParameters = {
groupId: string;
};
export type ListSpanMappersPathParameters = {
groupId: string;
};
export type ListSpanMappers200 = {
data: SpantypesGettableSpanMapperGroupsDTO;
/**
* @type string
*/
status: string;
};
export type CreateSpanMapperPathParameters = {
groupId: string;
};
export type CreateSpanMapper201 = {
data: SpantypesSpanMapperDTO;
/**
* @type string
*/
status: string;
};
export type DeleteSpanMapperPathParameters = {
groupId: string;
mapperId: string;
};
export type UpdateSpanMapperPathParameters = {
groupId: string;
mapperId: string;
};
export type ListUsersDeprecated200 = {
/**
* @type array

View File

@@ -0,0 +1,787 @@
/**
* ! Do not edit manually
* * The file has been auto-generated using Orval for SigNoz
* * regenerate with 'yarn generate:api'
* SigNoz
*/
import { useMutation, useQuery } from 'react-query';
import type {
InvalidateOptions,
MutationFunction,
QueryClient,
QueryFunction,
QueryKey,
UseMutationOptions,
UseMutationResult,
UseQueryOptions,
UseQueryResult,
} from 'react-query';
import type {
CreateSpanMapper201,
CreateSpanMapperGroup201,
CreateSpanMapperPathParameters,
DeleteSpanMapperGroupPathParameters,
DeleteSpanMapperPathParameters,
ListSpanMapperGroups200,
ListSpanMapperGroupsParams,
ListSpanMappers200,
ListSpanMappersPathParameters,
RenderErrorResponseDTO,
SpantypesPostableSpanMapperDTO,
SpantypesPostableSpanMapperGroupDTO,
SpantypesUpdatableSpanMapperDTO,
SpantypesUpdatableSpanMapperGroupDTO,
UpdateSpanMapperGroupPathParameters,
UpdateSpanMapperPathParameters,
} from '../sigNoz.schemas';
import { GeneratedAPIInstance } from '../../../generatedAPIInstance';
import type { ErrorType, BodyType } from '../../../generatedAPIInstance';
/**
* Returns all span attribute mapping groups for the authenticated org.
* @summary List span attribute mapping groups
*/
export const listSpanMapperGroups = (
params?: ListSpanMapperGroupsParams,
signal?: AbortSignal,
) => {
return GeneratedAPIInstance<ListSpanMapperGroups200>({
url: `/api/v1/span_mapper_groups`,
method: 'GET',
params,
signal,
});
};
export const getListSpanMapperGroupsQueryKey = (
params?: ListSpanMapperGroupsParams,
) => {
return [`/api/v1/span_mapper_groups`, ...(params ? [params] : [])] as const;
};
export const getListSpanMapperGroupsQueryOptions = <
TData = Awaited<ReturnType<typeof listSpanMapperGroups>>,
TError = ErrorType<RenderErrorResponseDTO>,
>(
params?: ListSpanMapperGroupsParams,
options?: {
query?: UseQueryOptions<
Awaited<ReturnType<typeof listSpanMapperGroups>>,
TError,
TData
>;
},
) => {
const { query: queryOptions } = options ?? {};
const queryKey =
queryOptions?.queryKey ?? getListSpanMapperGroupsQueryKey(params);
const queryFn: QueryFunction<
Awaited<ReturnType<typeof listSpanMapperGroups>>
> = ({ signal }) => listSpanMapperGroups(params, signal);
return { queryKey, queryFn, ...queryOptions } as UseQueryOptions<
Awaited<ReturnType<typeof listSpanMapperGroups>>,
TError,
TData
> & { queryKey: QueryKey };
};
export type ListSpanMapperGroupsQueryResult = NonNullable<
Awaited<ReturnType<typeof listSpanMapperGroups>>
>;
export type ListSpanMapperGroupsQueryError = ErrorType<RenderErrorResponseDTO>;
/**
* @summary List span attribute mapping groups
*/
export function useListSpanMapperGroups<
TData = Awaited<ReturnType<typeof listSpanMapperGroups>>,
TError = ErrorType<RenderErrorResponseDTO>,
>(
params?: ListSpanMapperGroupsParams,
options?: {
query?: UseQueryOptions<
Awaited<ReturnType<typeof listSpanMapperGroups>>,
TError,
TData
>;
},
): UseQueryResult<TData, TError> & { queryKey: QueryKey } {
const queryOptions = getListSpanMapperGroupsQueryOptions(params, options);
const query = useQuery(queryOptions) as UseQueryResult<TData, TError> & {
queryKey: QueryKey;
};
query.queryKey = queryOptions.queryKey;
return query;
}
/**
* @summary List span attribute mapping groups
*/
export const invalidateListSpanMapperGroups = async (
queryClient: QueryClient,
params?: ListSpanMapperGroupsParams,
options?: InvalidateOptions,
): Promise<QueryClient> => {
await queryClient.invalidateQueries(
{ queryKey: getListSpanMapperGroupsQueryKey(params) },
options,
);
return queryClient;
};
/**
* Creates a new span attribute mapping group for the org.
* @summary Create a span attribute mapping group
*/
export const createSpanMapperGroup = (
spantypesPostableSpanMapperGroupDTO: BodyType<SpantypesPostableSpanMapperGroupDTO>,
signal?: AbortSignal,
) => {
return GeneratedAPIInstance<CreateSpanMapperGroup201>({
url: `/api/v1/span_mapper_groups`,
method: 'POST',
headers: { 'Content-Type': 'application/json' },
data: spantypesPostableSpanMapperGroupDTO,
signal,
});
};
export const getCreateSpanMapperGroupMutationOptions = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown,
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof createSpanMapperGroup>>,
TError,
{ data: BodyType<SpantypesPostableSpanMapperGroupDTO> },
TContext
>;
}): UseMutationOptions<
Awaited<ReturnType<typeof createSpanMapperGroup>>,
TError,
{ data: BodyType<SpantypesPostableSpanMapperGroupDTO> },
TContext
> => {
const mutationKey = ['createSpanMapperGroup'];
const { mutation: mutationOptions } = options
? options.mutation &&
'mutationKey' in options.mutation &&
options.mutation.mutationKey
? options
: { ...options, mutation: { ...options.mutation, mutationKey } }
: { mutation: { mutationKey } };
const mutationFn: MutationFunction<
Awaited<ReturnType<typeof createSpanMapperGroup>>,
{ data: BodyType<SpantypesPostableSpanMapperGroupDTO> }
> = (props) => {
const { data } = props ?? {};
return createSpanMapperGroup(data);
};
return { mutationFn, ...mutationOptions };
};
export type CreateSpanMapperGroupMutationResult = NonNullable<
Awaited<ReturnType<typeof createSpanMapperGroup>>
>;
export type CreateSpanMapperGroupMutationBody =
BodyType<SpantypesPostableSpanMapperGroupDTO>;
export type CreateSpanMapperGroupMutationError =
ErrorType<RenderErrorResponseDTO>;
/**
* @summary Create a span attribute mapping group
*/
export const useCreateSpanMapperGroup = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown,
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof createSpanMapperGroup>>,
TError,
{ data: BodyType<SpantypesPostableSpanMapperGroupDTO> },
TContext
>;
}): UseMutationResult<
Awaited<ReturnType<typeof createSpanMapperGroup>>,
TError,
{ data: BodyType<SpantypesPostableSpanMapperGroupDTO> },
TContext
> => {
const mutationOptions = getCreateSpanMapperGroupMutationOptions(options);
return useMutation(mutationOptions);
};
/**
* Hard-deletes a mapping group and cascades to all its mappers.
* @summary Delete a span attribute mapping group
*/
export const deleteSpanMapperGroup = ({
groupId,
}: DeleteSpanMapperGroupPathParameters) => {
return GeneratedAPIInstance<void>({
url: `/api/v1/span_mapper_groups/${groupId}`,
method: 'DELETE',
});
};
export const getDeleteSpanMapperGroupMutationOptions = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown,
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof deleteSpanMapperGroup>>,
TError,
{ pathParams: DeleteSpanMapperGroupPathParameters },
TContext
>;
}): UseMutationOptions<
Awaited<ReturnType<typeof deleteSpanMapperGroup>>,
TError,
{ pathParams: DeleteSpanMapperGroupPathParameters },
TContext
> => {
const mutationKey = ['deleteSpanMapperGroup'];
const { mutation: mutationOptions } = options
? options.mutation &&
'mutationKey' in options.mutation &&
options.mutation.mutationKey
? options
: { ...options, mutation: { ...options.mutation, mutationKey } }
: { mutation: { mutationKey } };
const mutationFn: MutationFunction<
Awaited<ReturnType<typeof deleteSpanMapperGroup>>,
{ pathParams: DeleteSpanMapperGroupPathParameters }
> = (props) => {
const { pathParams } = props ?? {};
return deleteSpanMapperGroup(pathParams);
};
return { mutationFn, ...mutationOptions };
};
export type DeleteSpanMapperGroupMutationResult = NonNullable<
Awaited<ReturnType<typeof deleteSpanMapperGroup>>
>;
export type DeleteSpanMapperGroupMutationError =
ErrorType<RenderErrorResponseDTO>;
/**
* @summary Delete a span attribute mapping group
*/
export const useDeleteSpanMapperGroup = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown,
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof deleteSpanMapperGroup>>,
TError,
{ pathParams: DeleteSpanMapperGroupPathParameters },
TContext
>;
}): UseMutationResult<
Awaited<ReturnType<typeof deleteSpanMapperGroup>>,
TError,
{ pathParams: DeleteSpanMapperGroupPathParameters },
TContext
> => {
const mutationOptions = getDeleteSpanMapperGroupMutationOptions(options);
return useMutation(mutationOptions);
};
/**
* Partially updates an existing mapping group's name, condition, or enabled state.
* @summary Update a span attribute mapping group
*/
export const updateSpanMapperGroup = (
{ groupId }: UpdateSpanMapperGroupPathParameters,
spantypesUpdatableSpanMapperGroupDTO: BodyType<SpantypesUpdatableSpanMapperGroupDTO>,
) => {
return GeneratedAPIInstance<void>({
url: `/api/v1/span_mapper_groups/${groupId}`,
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
data: spantypesUpdatableSpanMapperGroupDTO,
});
};
export const getUpdateSpanMapperGroupMutationOptions = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown,
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof updateSpanMapperGroup>>,
TError,
{
pathParams: UpdateSpanMapperGroupPathParameters;
data: BodyType<SpantypesUpdatableSpanMapperGroupDTO>;
},
TContext
>;
}): UseMutationOptions<
Awaited<ReturnType<typeof updateSpanMapperGroup>>,
TError,
{
pathParams: UpdateSpanMapperGroupPathParameters;
data: BodyType<SpantypesUpdatableSpanMapperGroupDTO>;
},
TContext
> => {
const mutationKey = ['updateSpanMapperGroup'];
const { mutation: mutationOptions } = options
? options.mutation &&
'mutationKey' in options.mutation &&
options.mutation.mutationKey
? options
: { ...options, mutation: { ...options.mutation, mutationKey } }
: { mutation: { mutationKey } };
const mutationFn: MutationFunction<
Awaited<ReturnType<typeof updateSpanMapperGroup>>,
{
pathParams: UpdateSpanMapperGroupPathParameters;
data: BodyType<SpantypesUpdatableSpanMapperGroupDTO>;
}
> = (props) => {
const { pathParams, data } = props ?? {};
return updateSpanMapperGroup(pathParams, data);
};
return { mutationFn, ...mutationOptions };
};
export type UpdateSpanMapperGroupMutationResult = NonNullable<
Awaited<ReturnType<typeof updateSpanMapperGroup>>
>;
export type UpdateSpanMapperGroupMutationBody =
BodyType<SpantypesUpdatableSpanMapperGroupDTO>;
export type UpdateSpanMapperGroupMutationError =
ErrorType<RenderErrorResponseDTO>;
/**
* @summary Update a span attribute mapping group
*/
export const useUpdateSpanMapperGroup = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown,
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof updateSpanMapperGroup>>,
TError,
{
pathParams: UpdateSpanMapperGroupPathParameters;
data: BodyType<SpantypesUpdatableSpanMapperGroupDTO>;
},
TContext
>;
}): UseMutationResult<
Awaited<ReturnType<typeof updateSpanMapperGroup>>,
TError,
{
pathParams: UpdateSpanMapperGroupPathParameters;
data: BodyType<SpantypesUpdatableSpanMapperGroupDTO>;
},
TContext
> => {
const mutationOptions = getUpdateSpanMapperGroupMutationOptions(options);
return useMutation(mutationOptions);
};
/**
* Returns all mappers belonging to a mapping group.
* @summary List span mappers for a group
*/
export const listSpanMappers = (
{ groupId }: ListSpanMappersPathParameters,
signal?: AbortSignal,
) => {
return GeneratedAPIInstance<ListSpanMappers200>({
url: `/api/v1/span_mapper_groups/${groupId}/span_mappers`,
method: 'GET',
signal,
});
};
export const getListSpanMappersQueryKey = ({
groupId,
}: ListSpanMappersPathParameters) => {
return [`/api/v1/span_mapper_groups/${groupId}/span_mappers`] as const;
};
export const getListSpanMappersQueryOptions = <
TData = Awaited<ReturnType<typeof listSpanMappers>>,
TError = ErrorType<RenderErrorResponseDTO>,
>(
{ groupId }: ListSpanMappersPathParameters,
options?: {
query?: UseQueryOptions<
Awaited<ReturnType<typeof listSpanMappers>>,
TError,
TData
>;
},
) => {
const { query: queryOptions } = options ?? {};
const queryKey =
queryOptions?.queryKey ?? getListSpanMappersQueryKey({ groupId });
const queryFn: QueryFunction<Awaited<ReturnType<typeof listSpanMappers>>> = ({
signal,
}) => listSpanMappers({ groupId }, signal);
return {
queryKey,
queryFn,
enabled: !!groupId,
...queryOptions,
} as UseQueryOptions<
Awaited<ReturnType<typeof listSpanMappers>>,
TError,
TData
> & { queryKey: QueryKey };
};
export type ListSpanMappersQueryResult = NonNullable<
Awaited<ReturnType<typeof listSpanMappers>>
>;
export type ListSpanMappersQueryError = ErrorType<RenderErrorResponseDTO>;
/**
* @summary List span mappers for a group
*/
export function useListSpanMappers<
TData = Awaited<ReturnType<typeof listSpanMappers>>,
TError = ErrorType<RenderErrorResponseDTO>,
>(
{ groupId }: ListSpanMappersPathParameters,
options?: {
query?: UseQueryOptions<
Awaited<ReturnType<typeof listSpanMappers>>,
TError,
TData
>;
},
): UseQueryResult<TData, TError> & { queryKey: QueryKey } {
const queryOptions = getListSpanMappersQueryOptions({ groupId }, options);
const query = useQuery(queryOptions) as UseQueryResult<TData, TError> & {
queryKey: QueryKey;
};
query.queryKey = queryOptions.queryKey;
return query;
}
/**
* @summary List span mappers for a group
*/
export const invalidateListSpanMappers = async (
queryClient: QueryClient,
{ groupId }: ListSpanMappersPathParameters,
options?: InvalidateOptions,
): Promise<QueryClient> => {
await queryClient.invalidateQueries(
{ queryKey: getListSpanMappersQueryKey({ groupId }) },
options,
);
return queryClient;
};
/**
* Adds a new mapper to the specified mapping group.
* @summary Create a span mapper
*/
export const createSpanMapper = (
{ groupId }: CreateSpanMapperPathParameters,
spantypesPostableSpanMapperDTO: BodyType<SpantypesPostableSpanMapperDTO>,
signal?: AbortSignal,
) => {
return GeneratedAPIInstance<CreateSpanMapper201>({
url: `/api/v1/span_mapper_groups/${groupId}/span_mappers`,
method: 'POST',
headers: { 'Content-Type': 'application/json' },
data: spantypesPostableSpanMapperDTO,
signal,
});
};
export const getCreateSpanMapperMutationOptions = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown,
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof createSpanMapper>>,
TError,
{
pathParams: CreateSpanMapperPathParameters;
data: BodyType<SpantypesPostableSpanMapperDTO>;
},
TContext
>;
}): UseMutationOptions<
Awaited<ReturnType<typeof createSpanMapper>>,
TError,
{
pathParams: CreateSpanMapperPathParameters;
data: BodyType<SpantypesPostableSpanMapperDTO>;
},
TContext
> => {
const mutationKey = ['createSpanMapper'];
const { mutation: mutationOptions } = options
? options.mutation &&
'mutationKey' in options.mutation &&
options.mutation.mutationKey
? options
: { ...options, mutation: { ...options.mutation, mutationKey } }
: { mutation: { mutationKey } };
const mutationFn: MutationFunction<
Awaited<ReturnType<typeof createSpanMapper>>,
{
pathParams: CreateSpanMapperPathParameters;
data: BodyType<SpantypesPostableSpanMapperDTO>;
}
> = (props) => {
const { pathParams, data } = props ?? {};
return createSpanMapper(pathParams, data);
};
return { mutationFn, ...mutationOptions };
};
export type CreateSpanMapperMutationResult = NonNullable<
Awaited<ReturnType<typeof createSpanMapper>>
>;
export type CreateSpanMapperMutationBody =
BodyType<SpantypesPostableSpanMapperDTO>;
export type CreateSpanMapperMutationError = ErrorType<RenderErrorResponseDTO>;
/**
* @summary Create a span mapper
*/
export const useCreateSpanMapper = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown,
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof createSpanMapper>>,
TError,
{
pathParams: CreateSpanMapperPathParameters;
data: BodyType<SpantypesPostableSpanMapperDTO>;
},
TContext
>;
}): UseMutationResult<
Awaited<ReturnType<typeof createSpanMapper>>,
TError,
{
pathParams: CreateSpanMapperPathParameters;
data: BodyType<SpantypesPostableSpanMapperDTO>;
},
TContext
> => {
const mutationOptions = getCreateSpanMapperMutationOptions(options);
return useMutation(mutationOptions);
};
/**
* Hard-deletes a mapper from a mapping group.
* @summary Delete a span mapper
*/
export const deleteSpanMapper = ({
groupId,
mapperId,
}: DeleteSpanMapperPathParameters) => {
return GeneratedAPIInstance<void>({
url: `/api/v1/span_mapper_groups/${groupId}/span_mappers/${mapperId}`,
method: 'DELETE',
});
};
export const getDeleteSpanMapperMutationOptions = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown,
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof deleteSpanMapper>>,
TError,
{ pathParams: DeleteSpanMapperPathParameters },
TContext
>;
}): UseMutationOptions<
Awaited<ReturnType<typeof deleteSpanMapper>>,
TError,
{ pathParams: DeleteSpanMapperPathParameters },
TContext
> => {
const mutationKey = ['deleteSpanMapper'];
const { mutation: mutationOptions } = options
? options.mutation &&
'mutationKey' in options.mutation &&
options.mutation.mutationKey
? options
: { ...options, mutation: { ...options.mutation, mutationKey } }
: { mutation: { mutationKey } };
const mutationFn: MutationFunction<
Awaited<ReturnType<typeof deleteSpanMapper>>,
{ pathParams: DeleteSpanMapperPathParameters }
> = (props) => {
const { pathParams } = props ?? {};
return deleteSpanMapper(pathParams);
};
return { mutationFn, ...mutationOptions };
};
export type DeleteSpanMapperMutationResult = NonNullable<
Awaited<ReturnType<typeof deleteSpanMapper>>
>;
export type DeleteSpanMapperMutationError = ErrorType<RenderErrorResponseDTO>;
/**
* @summary Delete a span mapper
*/
export const useDeleteSpanMapper = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown,
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof deleteSpanMapper>>,
TError,
{ pathParams: DeleteSpanMapperPathParameters },
TContext
>;
}): UseMutationResult<
Awaited<ReturnType<typeof deleteSpanMapper>>,
TError,
{ pathParams: DeleteSpanMapperPathParameters },
TContext
> => {
const mutationOptions = getDeleteSpanMapperMutationOptions(options);
return useMutation(mutationOptions);
};
/**
* Partially updates an existing mapper's field context, config, or enabled state.
* @summary Update a span mapper
*/
export const updateSpanMapper = (
{ groupId, mapperId }: UpdateSpanMapperPathParameters,
spantypesUpdatableSpanMapperDTO: BodyType<SpantypesUpdatableSpanMapperDTO>,
) => {
return GeneratedAPIInstance<void>({
url: `/api/v1/span_mapper_groups/${groupId}/span_mappers/${mapperId}`,
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
data: spantypesUpdatableSpanMapperDTO,
});
};
export const getUpdateSpanMapperMutationOptions = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown,
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof updateSpanMapper>>,
TError,
{
pathParams: UpdateSpanMapperPathParameters;
data: BodyType<SpantypesUpdatableSpanMapperDTO>;
},
TContext
>;
}): UseMutationOptions<
Awaited<ReturnType<typeof updateSpanMapper>>,
TError,
{
pathParams: UpdateSpanMapperPathParameters;
data: BodyType<SpantypesUpdatableSpanMapperDTO>;
},
TContext
> => {
const mutationKey = ['updateSpanMapper'];
const { mutation: mutationOptions } = options
? options.mutation &&
'mutationKey' in options.mutation &&
options.mutation.mutationKey
? options
: { ...options, mutation: { ...options.mutation, mutationKey } }
: { mutation: { mutationKey } };
const mutationFn: MutationFunction<
Awaited<ReturnType<typeof updateSpanMapper>>,
{
pathParams: UpdateSpanMapperPathParameters;
data: BodyType<SpantypesUpdatableSpanMapperDTO>;
}
> = (props) => {
const { pathParams, data } = props ?? {};
return updateSpanMapper(pathParams, data);
};
return { mutationFn, ...mutationOptions };
};
export type UpdateSpanMapperMutationResult = NonNullable<
Awaited<ReturnType<typeof updateSpanMapper>>
>;
export type UpdateSpanMapperMutationBody =
BodyType<SpantypesUpdatableSpanMapperDTO>;
export type UpdateSpanMapperMutationError = ErrorType<RenderErrorResponseDTO>;
/**
* @summary Update a span mapper
*/
export const useUpdateSpanMapper = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown,
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof updateSpanMapper>>,
TError,
{
pathParams: UpdateSpanMapperPathParameters;
data: BodyType<SpantypesUpdatableSpanMapperDTO>;
},
TContext
>;
}): UseMutationResult<
Awaited<ReturnType<typeof updateSpanMapper>>,
TError,
{
pathParams: UpdateSpanMapperPathParameters;
data: BodyType<SpantypesUpdatableSpanMapperDTO>;
},
TContext
> => {
const mutationOptions = getUpdateSpanMapperMutationOptions(options);
return useMutation(mutationOptions);
};

View File

@@ -1,93 +0,0 @@
package signozapiserver
import (
"net/http"
"github.com/SigNoz/signoz/pkg/http/handler"
"github.com/SigNoz/signoz/pkg/types"
"github.com/SigNoz/signoz/pkg/types/llmpricingruletypes"
"github.com/gorilla/mux"
)
func (provider *provider) addLLMPricingRuleRoutes(router *mux.Router) error {
if err := router.Handle("/api/v1/llm_pricing_rules", handler.New(
provider.authZ.ViewAccess(provider.llmPricingRuleHandler.List),
handler.OpenAPIDef{
ID: "ListLLMPricingRules",
Tags: []string{"llmpricingrules"},
Summary: "List pricing rules",
Description: "Returns all LLM pricing rules for the authenticated org, with pagination.",
Request: nil,
RequestContentType: "",
RequestQuery: new(llmpricingruletypes.ListPricingRulesQuery),
Response: new(llmpricingruletypes.GettablePricingRules),
ResponseContentType: "application/json",
SuccessStatusCode: http.StatusOK,
ErrorStatusCodes: []int{http.StatusBadRequest},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(types.RoleViewer),
},
)).Methods(http.MethodGet).GetError(); err != nil {
return err
}
if err := router.Handle("/api/v1/llm_pricing_rules", handler.New(
provider.authZ.AdminAccess(provider.llmPricingRuleHandler.Update),
handler.OpenAPIDef{
ID: "UpdateLLMPricingRules",
Tags: []string{"llmpricingrules"},
Summary: "Bulk update pricing rules",
Description: "Single write endpoint used by both the user and the Zeus sync job. Per-rule match is by id, then sourceId, then insert. Override rows (is_override=true) are fully preserved when the request does not provide isOverride; only synced_at is stamped.",
Request: new(llmpricingruletypes.UpdatableLLMPricingRules),
RequestContentType: "application/json",
SuccessStatusCode: http.StatusNoContent,
ErrorStatusCodes: []int{http.StatusBadRequest},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
},
)).Methods(http.MethodPut).GetError(); err != nil {
return err
}
if err := router.Handle("/api/v1/llm_pricing_rules/{id}", handler.New(
provider.authZ.ViewAccess(provider.llmPricingRuleHandler.Get),
handler.OpenAPIDef{
ID: "GetLLMPricingRule",
Tags: []string{"llmpricingrules"},
Summary: "Get a pricing rule",
Description: "Returns a single LLM pricing rule by ID.",
Request: nil,
RequestContentType: "",
Response: new(llmpricingruletypes.GettableLLMPricingRule),
ResponseContentType: "application/json",
SuccessStatusCode: http.StatusOK,
ErrorStatusCodes: []int{http.StatusNotFound},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(types.RoleViewer),
},
)).Methods(http.MethodGet).GetError(); err != nil {
return err
}
if err := router.Handle("/api/v1/llm_pricing_rules/{id}", handler.New(
provider.authZ.AdminAccess(provider.llmPricingRuleHandler.Delete),
handler.OpenAPIDef{
ID: "DeleteLLMPricingRule",
Tags: []string{"llmpricingrules"},
Summary: "Delete a pricing rule",
Description: "Hard-deletes a pricing rule. If auto-synced, it will be recreated on the next sync cycle.",
Request: nil,
RequestContentType: "",
Response: nil,
ResponseContentType: "",
SuccessStatusCode: http.StatusNoContent,
ErrorStatusCodes: []int{http.StatusNotFound},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
},
)).Methods(http.MethodDelete).GetError(); err != nil {
return err
}
return nil
}

View File

@@ -17,7 +17,6 @@ import (
"github.com/SigNoz/signoz/pkg/modules/dashboard"
"github.com/SigNoz/signoz/pkg/modules/fields"
"github.com/SigNoz/signoz/pkg/modules/inframonitoring"
"github.com/SigNoz/signoz/pkg/modules/llmpricingrule"
"github.com/SigNoz/signoz/pkg/modules/metricsexplorer"
"github.com/SigNoz/signoz/pkg/modules/organization"
"github.com/SigNoz/signoz/pkg/modules/preference"
@@ -26,6 +25,7 @@ import (
"github.com/SigNoz/signoz/pkg/modules/rulestatehistory"
"github.com/SigNoz/signoz/pkg/modules/serviceaccount"
"github.com/SigNoz/signoz/pkg/modules/session"
"github.com/SigNoz/signoz/pkg/modules/spanmapper"
"github.com/SigNoz/signoz/pkg/modules/tracedetail"
"github.com/SigNoz/signoz/pkg/modules/user"
"github.com/SigNoz/signoz/pkg/querier"
@@ -63,10 +63,10 @@ type provider struct {
factoryHandler factory.Handler
cloudIntegrationHandler cloudintegration.Handler
ruleStateHistoryHandler rulestatehistory.Handler
spanMapperHandler spanmapper.Handler
alertmanagerHandler alertmanager.Handler
traceDetailHandler tracedetail.Handler
rulerHandler ruler.Handler
llmPricingRuleHandler llmpricingrule.Handler
}
func NewFactory(
@@ -94,8 +94,8 @@ func NewFactory(
factoryHandler factory.Handler,
cloudIntegrationHandler cloudintegration.Handler,
ruleStateHistoryHandler rulestatehistory.Handler,
spanMapperHandler spanmapper.Handler,
alertmanagerHandler alertmanager.Handler,
llmPricingRuleHandler llmpricingrule.Handler,
traceDetailHandler tracedetail.Handler,
rulerHandler ruler.Handler,
) factory.ProviderFactory[apiserver.APIServer, apiserver.Config] {
@@ -128,8 +128,8 @@ func NewFactory(
factoryHandler,
cloudIntegrationHandler,
ruleStateHistoryHandler,
spanMapperHandler,
alertmanagerHandler,
llmPricingRuleHandler,
traceDetailHandler,
rulerHandler,
)
@@ -164,8 +164,8 @@ func newProvider(
factoryHandler factory.Handler,
cloudIntegrationHandler cloudintegration.Handler,
ruleStateHistoryHandler rulestatehistory.Handler,
spanMapperHandler spanmapper.Handler,
alertmanagerHandler alertmanager.Handler,
llmPricingRuleHandler llmpricingrule.Handler,
traceDetailHandler tracedetail.Handler,
rulerHandler ruler.Handler,
) (apiserver.APIServer, error) {
@@ -198,10 +198,10 @@ func newProvider(
factoryHandler: factoryHandler,
cloudIntegrationHandler: cloudIntegrationHandler,
ruleStateHistoryHandler: ruleStateHistoryHandler,
spanMapperHandler: spanMapperHandler,
alertmanagerHandler: alertmanagerHandler,
traceDetailHandler: traceDetailHandler,
rulerHandler: rulerHandler,
llmPricingRuleHandler: llmPricingRuleHandler,
}
provider.authZ = middleware.NewAuthZ(settings.Logger(), orgGetter, authz)
@@ -306,11 +306,11 @@ func (provider *provider) AddToRouter(router *mux.Router) error {
return err
}
if err := provider.addAlertmanagerRoutes(router); err != nil {
if err := provider.addSpanMapperRoutes(router); err != nil {
return err
}
if err := provider.addLLMPricingRuleRoutes(router); err != nil {
if err := provider.addAlertmanagerRoutes(router); err != nil {
return err
}

View File

@@ -0,0 +1,171 @@
package signozapiserver
import (
"net/http"
"github.com/SigNoz/signoz/pkg/http/handler"
"github.com/SigNoz/signoz/pkg/types"
"github.com/SigNoz/signoz/pkg/types/spantypes"
"github.com/gorilla/mux"
)
func (provider *provider) addSpanMapperRoutes(router *mux.Router) error {
if err := router.Handle("/api/v1/span_mapper_groups", handler.New(
provider.authZ.ViewAccess(provider.spanMapperHandler.ListGroups),
handler.OpenAPIDef{
ID: "ListSpanMapperGroups",
Tags: []string{"spanmapper"},
Summary: "List span attribute mapping groups",
Description: "Returns all span attribute mapping groups for the authenticated org.",
Request: nil,
RequestContentType: "",
RequestQuery: new(spantypes.ListSpanMapperGroupsQuery),
Response: new(spantypes.GettableSpanMapperGroups),
ResponseContentType: "application/json",
SuccessStatusCode: http.StatusOK,
ErrorStatusCodes: []int{http.StatusBadRequest},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(types.RoleViewer),
},
)).Methods(http.MethodGet).GetError(); err != nil {
return err
}
if err := router.Handle("/api/v1/span_mapper_groups", handler.New(
provider.authZ.AdminAccess(provider.spanMapperHandler.CreateGroup),
handler.OpenAPIDef{
ID: "CreateSpanMapperGroup",
Tags: []string{"spanmapper"},
Summary: "Create a span attribute mapping group",
Description: "Creates a new span attribute mapping group for the org.",
Request: new(spantypes.PostableSpanMapperGroup),
RequestContentType: "application/json",
Response: new(spantypes.GettableSpanMapperGroup),
ResponseContentType: "application/json",
SuccessStatusCode: http.StatusCreated,
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusConflict},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
},
)).Methods(http.MethodPost).GetError(); err != nil {
return err
}
if err := router.Handle("/api/v1/span_mapper_groups/{groupId}", handler.New(
provider.authZ.AdminAccess(provider.spanMapperHandler.UpdateGroup),
handler.OpenAPIDef{
ID: "UpdateSpanMapperGroup",
Tags: []string{"spanmapper"},
Summary: "Update a span attribute mapping group",
Description: "Partially updates an existing mapping group's name, condition, or enabled state.",
Request: new(spantypes.UpdatableSpanMapperGroup),
RequestContentType: "application/json",
SuccessStatusCode: http.StatusNoContent,
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusNotFound},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
},
)).Methods(http.MethodPatch).GetError(); err != nil {
return err
}
if err := router.Handle("/api/v1/span_mapper_groups/{groupId}", handler.New(
provider.authZ.AdminAccess(provider.spanMapperHandler.DeleteGroup),
handler.OpenAPIDef{
ID: "DeleteSpanMapperGroup",
Tags: []string{"spanmapper"},
Summary: "Delete a span attribute mapping group",
Description: "Hard-deletes a mapping group and cascades to all its mappers.",
Request: nil,
RequestContentType: "",
Response: nil,
ResponseContentType: "",
SuccessStatusCode: http.StatusNoContent,
ErrorStatusCodes: []int{http.StatusNotFound},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
},
)).Methods(http.MethodDelete).GetError(); err != nil {
return err
}
if err := router.Handle("/api/v1/span_mapper_groups/{groupId}/span_mappers", handler.New(
provider.authZ.ViewAccess(provider.spanMapperHandler.ListMappers),
handler.OpenAPIDef{
ID: "ListSpanMappers",
Tags: []string{"spanmapper"},
Summary: "List span mappers for a group",
Description: "Returns all mappers belonging to a mapping group.",
Request: nil,
RequestContentType: "",
Response: new(spantypes.GettableSpanMapperGroups),
ResponseContentType: "application/json",
SuccessStatusCode: http.StatusOK,
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusNotFound},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(types.RoleViewer),
},
)).Methods(http.MethodGet).GetError(); err != nil {
return err
}
if err := router.Handle("/api/v1/span_mapper_groups/{groupId}/span_mappers", handler.New(
provider.authZ.AdminAccess(provider.spanMapperHandler.CreateMapper),
handler.OpenAPIDef{
ID: "CreateSpanMapper",
Tags: []string{"spanmapper"},
Summary: "Create a span mapper",
Description: "Adds a new mapper to the specified mapping group.",
Request: new(spantypes.PostableSpanMapper),
RequestContentType: "application/json",
Response: new(spantypes.GettableSpanMapper),
ResponseContentType: "application/json",
SuccessStatusCode: http.StatusCreated,
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusNotFound, http.StatusConflict},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
},
)).Methods(http.MethodPost).GetError(); err != nil {
return err
}
if err := router.Handle("/api/v1/span_mapper_groups/{groupId}/span_mappers/{mapperId}", handler.New(
provider.authZ.AdminAccess(provider.spanMapperHandler.UpdateMapper),
handler.OpenAPIDef{
ID: "UpdateSpanMapper",
Tags: []string{"spanmapper"},
Summary: "Update a span mapper",
Description: "Partially updates an existing mapper's field context, config, or enabled state.",
Request: new(spantypes.UpdatableSpanMapper),
RequestContentType: "application/json",
SuccessStatusCode: http.StatusNoContent,
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusNotFound},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
},
)).Methods(http.MethodPatch).GetError(); err != nil {
return err
}
if err := router.Handle("/api/v1/span_mapper_groups/{groupId}/span_mappers/{mapperId}", handler.New(
provider.authZ.AdminAccess(provider.spanMapperHandler.DeleteMapper),
handler.OpenAPIDef{
ID: "DeleteSpanMapper",
Tags: []string{"spanmapper"},
Summary: "Delete a span mapper",
Description: "Hard-deletes a mapper from a mapping group.",
Request: nil,
RequestContentType: "",
Response: nil,
ResponseContentType: "",
SuccessStatusCode: http.StatusNoContent,
ErrorStatusCodes: []int{http.StatusNotFound},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
},
)).Methods(http.MethodDelete).GetError(); err != nil {
return err
}
return nil
}

View File

@@ -1,158 +0,0 @@
package impllmpricingrule
import (
"context"
"net/http"
"time"
"github.com/SigNoz/signoz/pkg/errors"
"github.com/SigNoz/signoz/pkg/factory"
"github.com/SigNoz/signoz/pkg/http/binding"
"github.com/SigNoz/signoz/pkg/http/render"
"github.com/SigNoz/signoz/pkg/modules/llmpricingrule"
"github.com/SigNoz/signoz/pkg/types/authtypes"
"github.com/SigNoz/signoz/pkg/types/llmpricingruletypes"
"github.com/SigNoz/signoz/pkg/valuer"
"github.com/gorilla/mux"
)
const maxLimit = 100
type handler struct {
module llmpricingrule.Module
providerSettings factory.ProviderSettings
}
func NewHandler(module llmpricingrule.Module, providerSettings factory.ProviderSettings) llmpricingrule.Handler {
return &handler{module: module, providerSettings: providerSettings}
}
// List handles GET /api/v1/llm_pricing_rules.
func (h *handler) List(rw http.ResponseWriter, r *http.Request) {
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
defer cancel()
claims, err := authtypes.ClaimsFromContext(ctx)
if err != nil {
render.Error(rw, err)
return
}
orgID := valuer.MustNewUUID(claims.OrgID)
var q llmpricingruletypes.ListPricingRulesQuery
if err := binding.Query.BindQuery(r.URL.Query(), &q); err != nil {
render.Error(rw, err)
return
}
if q.Limit <= 0 {
q.Limit = 20
} else if q.Limit > maxLimit {
q.Limit = maxLimit
}
if q.Offset < 0 {
render.Error(rw, errors.Newf(errors.TypeInvalidInput, llmpricingruletypes.ErrCodePricingRuleInvalidInput, "offset must be a non-negative integer"))
return
}
rules, total, err := h.module.List(ctx, orgID, q.Offset, q.Limit)
if err != nil {
render.Error(rw, err)
return
}
render.Success(rw, http.StatusOK, llmpricingruletypes.NewGettableLLMPricingRulesFromLLMPricingRules(rules, total, q.Offset, q.Limit))
}
// Get handles GET /api/v1/llm_pricing_rules/{id}.
func (h *handler) Get(rw http.ResponseWriter, r *http.Request) {
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
defer cancel()
claims, err := authtypes.ClaimsFromContext(ctx)
if err != nil {
render.Error(rw, err)
return
}
orgID := valuer.MustNewUUID(claims.OrgID)
id, err := ruleIDFromPath(r)
if err != nil {
render.Error(rw, err)
return
}
rule, err := h.module.Get(ctx, orgID, id)
if err != nil {
render.Error(rw, err)
return
}
render.Success(rw, http.StatusOK, rule)
}
func (h *handler) Update(rw http.ResponseWriter, r *http.Request) {
ctx, cancel := context.WithTimeout(r.Context(), 30*time.Second)
defer cancel()
claims, err := authtypes.ClaimsFromContext(ctx)
if err != nil {
render.Error(rw, err)
return
}
orgID := valuer.MustNewUUID(claims.OrgID)
req := new(llmpricingruletypes.UpdatableLLMPricingRules)
if err := binding.JSON.BindBody(r.Body, req); err != nil {
render.Error(rw, err)
return
}
err = h.module.Update(ctx, orgID, claims.Email, req.Rules)
if err != nil {
render.Error(rw, err)
return
}
render.Success(rw, http.StatusNoContent, nil)
}
// Delete handles DELETE /api/v1/llm_pricing_rules/{id}.
func (h *handler) Delete(rw http.ResponseWriter, r *http.Request) {
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
defer cancel()
claims, err := authtypes.ClaimsFromContext(ctx)
if err != nil {
render.Error(rw, err)
return
}
orgID := valuer.MustNewUUID(claims.OrgID)
id, err := ruleIDFromPath(r)
if err != nil {
render.Error(rw, err)
return
}
if err := h.module.Delete(ctx, orgID, id); err != nil {
render.Error(rw, err)
return
}
render.Success(rw, http.StatusNoContent, nil)
}
// ruleIDFromPath extracts and validates the {id} path variable.
func ruleIDFromPath(r *http.Request) (valuer.UUID, error) {
raw := mux.Vars(r)["id"]
id, err := valuer.NewUUID(raw)
if err != nil {
return valuer.UUID{}, errors.Wrapf(err, errors.TypeInvalidInput, llmpricingruletypes.ErrCodePricingRuleInvalidInput, "id is not a valid uuid")
}
return id, nil
}

View File

@@ -1,24 +0,0 @@
package llmpricingrule
import (
"context"
"net/http"
"github.com/SigNoz/signoz/pkg/types/llmpricingruletypes"
"github.com/SigNoz/signoz/pkg/valuer"
)
type Module interface {
List(ctx context.Context, orgID valuer.UUID, offset, limit int) ([]*llmpricingruletypes.LLMPricingRule, int, error)
Get(ctx context.Context, orgID valuer.UUID, id valuer.UUID) (*llmpricingruletypes.LLMPricingRule, error)
Update(ctx context.Context, orgID valuer.UUID, userEmail string, rules []llmpricingruletypes.UpdatableLLMPricingRule) (err error)
Delete(ctx context.Context, orgID, id valuer.UUID) error
}
// Handler defines the HTTP handler interface for pricing rule endpoints.
type Handler interface {
List(rw http.ResponseWriter, r *http.Request)
Get(rw http.ResponseWriter, r *http.Request)
Update(rw http.ResponseWriter, r *http.Request)
Delete(rw http.ResponseWriter, r *http.Request)
}

View File

@@ -0,0 +1,85 @@
package implspanmapper
import (
"context"
"encoding/json"
"github.com/SigNoz/signoz/pkg/modules/spanmapper"
"github.com/SigNoz/signoz/pkg/query-service/agentConf"
"github.com/SigNoz/signoz/pkg/types/opamptypes"
"github.com/SigNoz/signoz/pkg/types/spantypes"
"github.com/SigNoz/signoz/pkg/valuer"
)
const SpanAttrMappingFeatureType agentConf.AgentFeatureType = "span_attr_mapping"
// SpanAttrMappingFeature implements agentConf.AgentFeature. It reads enabled
// mapping groups and mappers from the module and generates the
// signozspanmappingprocessor config for deployment via OpAMP.
type SpanAttrMappingFeature struct {
module spanmapper.Module
}
func NewSpanAttrMappingFeature(module spanmapper.Module) *SpanAttrMappingFeature {
return &SpanAttrMappingFeature{module: module}
}
func (f *SpanAttrMappingFeature) AgentFeatureType() agentConf.AgentFeatureType {
return SpanAttrMappingFeatureType
}
func (f *SpanAttrMappingFeature) RecommendAgentConfig(
orgId valuer.UUID,
currentConfYaml []byte,
configVersion *opamptypes.AgentConfigVersion,
) ([]byte, string, error) {
ctx := context.Background()
groups, err := f.getEnabled(ctx, orgId)
if err != nil {
return nil, "", err
}
updatedConf, err := generateCollectorConfigWithSpanMapping(currentConfYaml, groups)
if err != nil {
return nil, "", err
}
serialized, err := json.Marshal(groups)
if err != nil {
return nil, "", err
}
return updatedConf, string(serialized), nil
}
// getEnabled returns enabled groups alongside their enabled mappers. Groups
// with no enabled mappers are still included so the collector sees the
// exists_any condition, even if the attributes list is empty.
func (f *SpanAttrMappingFeature) getEnabled(ctx context.Context, orgId valuer.UUID) ([]enabledGroup, error) {
if f.module == nil {
return nil, nil
}
enabled := true
groups, err := f.module.ListGroups(ctx, orgId, &spantypes.ListSpanMapperGroupsQuery{Enabled: &enabled})
if err != nil {
return nil, err
}
out := make([]enabledGroup, 0, len(groups))
for _, g := range groups {
mappers, err := f.module.ListMappers(ctx, orgId, g.ID)
if err != nil {
return nil, err
}
enabledMappers := make([]*spantypes.SpanMapper, 0, len(mappers))
for _, m := range mappers {
if m.Enabled {
enabledMappers = append(enabledMappers, m)
}
}
out = append(out, enabledGroup{group: g, mappers: enabledMappers})
}
return out, nil
}

View File

@@ -0,0 +1,137 @@
package implspanmapper
import (
"bytes"
"fmt"
"sort"
"github.com/SigNoz/signoz/pkg/types/spantypes"
"gopkg.in/yaml.v3"
)
const processorName = "signozspanmappingprocessor"
const (
// Collector context values (see signozspanmappingprocessor.ContextAttributes/ContextResource).
ctxAttributes = "attributes"
ctxResource = "resource"
// Collector action values.
actionCopy = "copy"
actionMove = "move"
// Source key prefix the collector treats as "read from resource".
resourcePrefix = "resource."
)
// enabledGroup pairs an enabled group with its enabled mappers.
type enabledGroup struct {
group *spantypes.SpanMapperGroup
mappers []*spantypes.SpanMapper
}
// buildProcessorConfig converts enabled groups + mappers into the
// signozspanmappingprocessor config shape.
func buildProcessorConfig(groups []enabledGroup) *spantypes.SpanMappingProcessorConfig {
out := make([]spantypes.SpanMappingGroup, 0, len(groups))
for _, eg := range groups {
rules := make([]spantypes.SpanMappingAttribute, 0, len(eg.mappers))
for _, m := range eg.mappers {
rules = append(rules, buildAttributeRule(m))
}
out = append(out, spantypes.SpanMappingGroup{
ID: eg.group.Name,
ExistsAny: spantypes.SpanMappingExistsAny{
Attributes: eg.group.Condition.Attributes,
Resource: eg.group.Condition.Resource,
},
Attributes: rules,
})
}
return &spantypes.SpanMappingProcessorConfig{Groups: out}
}
// buildAttributeRule maps a single SpanMapper to a collector AttributeRule.
// Sources are sorted by Priority DESC (highest-priority first), and read-from-
// resource sources are encoded via the "resource." prefix. The rule-level
// action is derived from the sources' operations (all sources within one
// mapper are expected to share the same operation; the highest-priority
// source's operation is used).
func buildAttributeRule(m *spantypes.SpanMapper) spantypes.SpanMappingAttribute {
sources := make([]spantypes.SpanMapperSource, len(m.Config.Sources))
copy(sources, m.Config.Sources)
sort.SliceStable(sources, func(i, j int) bool { return sources[i].Priority > sources[j].Priority })
keys := make([]string, 0, len(sources))
for _, s := range sources {
if s.Context == spantypes.FieldContextResource {
keys = append(keys, resourcePrefix+s.Key)
} else {
keys = append(keys, s.Key)
}
}
action := actionCopy
if len(sources) > 0 && sources[0].Operation == spantypes.SpanMapperOperationMove {
action = actionMove
}
ctx := ctxAttributes
if m.FieldContext == spantypes.FieldContextResource {
ctx = ctxResource
}
return spantypes.SpanMappingAttribute{
Target: m.Name,
Context: ctx,
Action: action,
Sources: keys,
}
}
// generateCollectorConfigWithSpanMapping injects (or replaces) the
// signozspanmappingprocessor block in the collector YAML. Pipeline wiring is
// handled by the collector's baseline config, not here.
func generateCollectorConfigWithSpanMapping(
currentConfYaml []byte,
groups []enabledGroup,
) ([]byte, error) {
// Empty input: nothing to inject into. Pass through unchanged so we don't
// turn it into "null\n" or fail on yaml.v3's EOF.
if len(bytes.TrimSpace(currentConfYaml)) == 0 {
return currentConfYaml, nil
}
var collectorConf map[string]any
if err := yaml.Unmarshal(currentConfYaml, &collectorConf); err != nil {
return nil, fmt.Errorf("failed to unmarshal collector config: %w", err)
}
if collectorConf == nil {
collectorConf = map[string]any{}
}
processors := map[string]any{}
if collectorConf["processors"] != nil {
if p, ok := collectorConf["processors"].(map[string]any); ok {
processors = p
}
}
procConfig := buildProcessorConfig(groups)
configBytes, err := yaml.Marshal(procConfig)
if err != nil {
return nil, fmt.Errorf("failed to marshal span attr mapping processor config: %w", err)
}
var configMap any
if err := yaml.Unmarshal(configBytes, &configMap); err != nil {
return nil, fmt.Errorf("failed to re-unmarshal span attr mapping processor config: %w", err)
}
processors[processorName] = configMap
collectorConf["processors"] = processors
return yaml.Marshal(collectorConf)
}

View File

@@ -0,0 +1,301 @@
package implspanmapper
import (
"context"
"net/http"
"time"
"github.com/SigNoz/signoz/pkg/errors"
"github.com/SigNoz/signoz/pkg/factory"
"github.com/SigNoz/signoz/pkg/http/binding"
"github.com/SigNoz/signoz/pkg/http/render"
"github.com/SigNoz/signoz/pkg/modules/spanmapper"
"github.com/SigNoz/signoz/pkg/types/authtypes"
"github.com/SigNoz/signoz/pkg/types/spantypes"
"github.com/SigNoz/signoz/pkg/valuer"
"github.com/gorilla/mux"
)
type handler struct {
module spanmapper.Module
providerSettings factory.ProviderSettings
}
func NewHandler(module spanmapper.Module, providerSettings factory.ProviderSettings) spanmapper.Handler {
return &handler{module: module, providerSettings: providerSettings}
}
// ListGroups handles GET /api/v1/span_mapper_groups.
func (h *handler) ListGroups(rw http.ResponseWriter, r *http.Request) {
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
defer cancel()
claims, err := authtypes.ClaimsFromContext(ctx)
if err != nil {
render.Error(rw, err)
return
}
orgID := valuer.MustNewUUID(claims.OrgID)
var q spantypes.ListSpanMapperGroupsQuery
if err := binding.Query.BindQuery(r.URL.Query(), &q); err != nil {
render.Error(rw, err)
return
}
groups, err := h.module.ListGroups(ctx, orgID, &q)
if err != nil {
render.Error(rw, err)
return
}
render.Success(rw, http.StatusOK, spantypes.NewGettableSpanMapperGroups(groups))
}
// CreateGroup handles POST /api/v1/span_mapper_groups.
func (h *handler) CreateGroup(rw http.ResponseWriter, r *http.Request) {
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
defer cancel()
claims, err := authtypes.ClaimsFromContext(ctx)
if err != nil {
render.Error(rw, err)
return
}
orgID := valuer.MustNewUUID(claims.OrgID)
req := new(spantypes.PostableSpanMapperGroup)
if err := binding.JSON.BindBody(r.Body, req); err != nil {
render.Error(rw, err)
return
}
group := spantypes.NewSpanMapperGroupFromPostable(req)
err = h.module.CreateGroup(ctx, orgID, claims.Email, group)
if err != nil {
render.Error(rw, err)
return
}
render.Success(rw, http.StatusCreated, group)
}
// UpdateGroup handles PUT /api/v1/span_mapper_groups/{id}.
func (h *handler) UpdateGroup(rw http.ResponseWriter, r *http.Request) {
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
defer cancel()
claims, err := authtypes.ClaimsFromContext(ctx)
if err != nil {
render.Error(rw, err)
return
}
orgID := valuer.MustNewUUID(claims.OrgID)
id, err := groupIDFromPath(r)
if err != nil {
render.Error(rw, err)
return
}
req := new(spantypes.UpdatableSpanMapperGroup)
if err := binding.JSON.BindBody(r.Body, req); err != nil {
render.Error(rw, err)
return
}
err = h.module.UpdateGroup(ctx, orgID, id, claims.Email, spantypes.NewSpanMapperGroupFromUpdatable(req))
if err != nil {
render.Error(rw, err)
return
}
render.Success(rw, http.StatusNoContent, nil)
}
// DeleteGroup handles DELETE /api/v1/span_mapper_groups/{id}.
func (h *handler) DeleteGroup(rw http.ResponseWriter, r *http.Request) {
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
defer cancel()
claims, err := authtypes.ClaimsFromContext(ctx)
if err != nil {
render.Error(rw, err)
return
}
orgID := valuer.MustNewUUID(claims.OrgID)
id, err := groupIDFromPath(r)
if err != nil {
render.Error(rw, err)
return
}
if err := h.module.DeleteGroup(ctx, orgID, id); err != nil {
render.Error(rw, err)
return
}
render.Success(rw, http.StatusNoContent, nil)
}
// ListMappers handles GET /api/v1/span_mapper_groups/{id}/span_mappers.
func (h *handler) ListMappers(rw http.ResponseWriter, r *http.Request) {
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
defer cancel()
claims, err := authtypes.ClaimsFromContext(ctx)
if err != nil {
render.Error(rw, err)
return
}
orgID := valuer.MustNewUUID(claims.OrgID)
groupID, err := groupIDFromPath(r)
if err != nil {
render.Error(rw, err)
return
}
mappers, err := h.module.ListMappers(ctx, orgID, groupID)
if err != nil {
render.Error(rw, err)
return
}
render.Success(rw, http.StatusOK, spantypes.NewGettableSpanMappers(mappers))
}
// CreateMapper handles POST /api/v1/span_mapper_groups/{id}/span_mappers.
func (h *handler) CreateMapper(rw http.ResponseWriter, r *http.Request) {
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
defer cancel()
claims, err := authtypes.ClaimsFromContext(ctx)
if err != nil {
render.Error(rw, err)
return
}
orgID := valuer.MustNewUUID(claims.OrgID)
groupID, err := groupIDFromPath(r)
if err != nil {
render.Error(rw, err)
return
}
req := new(spantypes.PostableSpanMapper)
if err := binding.JSON.BindBody(r.Body, req); err != nil {
render.Error(rw, err)
return
}
mapper := spantypes.NewSpanMapperFromPostable(req)
err = h.module.CreateMapper(ctx, orgID, groupID, claims.Email, mapper)
if err != nil {
render.Error(rw, err)
return
}
render.Success(rw, http.StatusCreated, mapper)
}
// UpdateMapper handles PUT /api/v1/span_mapper_groups/{groupId}/span_mappers/{mapperId}.
func (h *handler) UpdateMapper(rw http.ResponseWriter, r *http.Request) {
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
defer cancel()
claims, err := authtypes.ClaimsFromContext(ctx)
if err != nil {
render.Error(rw, err)
return
}
orgID := valuer.MustNewUUID(claims.OrgID)
groupID, err := groupIDFromPath(r)
if err != nil {
render.Error(rw, err)
return
}
mapperID, err := mapperIDFromPath(r)
if err != nil {
render.Error(rw, err)
return
}
req := new(spantypes.UpdatableSpanMapper)
if err := binding.JSON.BindBody(r.Body, req); err != nil {
render.Error(rw, err)
return
}
err = h.module.UpdateMapper(ctx, orgID, groupID, mapperID, claims.Email, spantypes.NewSpanMapperFromUpdatable(req))
if err != nil {
render.Error(rw, err)
return
}
render.Success(rw, http.StatusNoContent, nil)
}
// DeleteMapper handles DELETE /api/v1/span_mapper_groups/{groupId}/span_mappers/{mapperId}.
func (h *handler) DeleteMapper(rw http.ResponseWriter, r *http.Request) {
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
defer cancel()
claims, err := authtypes.ClaimsFromContext(ctx)
if err != nil {
render.Error(rw, err)
return
}
orgID := valuer.MustNewUUID(claims.OrgID)
groupID, err := groupIDFromPath(r)
if err != nil {
render.Error(rw, err)
return
}
mapperID, err := mapperIDFromPath(r)
if err != nil {
render.Error(rw, err)
return
}
if err := h.module.DeleteMapper(ctx, orgID, groupID, mapperID); err != nil {
render.Error(rw, err)
return
}
render.Success(rw, http.StatusNoContent, nil)
}
// groupIDFromPath extracts and validates the {id} or {groupId} path variable.
func groupIDFromPath(r *http.Request) (valuer.UUID, error) {
vars := mux.Vars(r)
raw := vars["groupId"]
id, err := valuer.NewUUID(raw)
if err != nil {
return valuer.UUID{}, errors.Wrapf(err, errors.TypeInvalidInput, spantypes.ErrCodeMappingInvalidInput, "group id is not a valid uuid")
}
return id, nil
}
// mapperIDFromPath extracts and validates the {mapperId} path variable.
func mapperIDFromPath(r *http.Request) (valuer.UUID, error) {
raw := mux.Vars(r)["mapperId"]
id, err := valuer.NewUUID(raw)
if err != nil {
return valuer.UUID{}, errors.Wrapf(err, errors.TypeInvalidInput, spantypes.ErrCodeMappingInvalidInput, "mapper id is not a valid uuid")
}
return id, nil
}

View File

@@ -0,0 +1,179 @@
package implspanmapper
import (
"context"
"time"
"github.com/SigNoz/signoz/pkg/modules/spanmapper"
"github.com/SigNoz/signoz/pkg/query-service/agentConf"
"github.com/SigNoz/signoz/pkg/types"
"github.com/SigNoz/signoz/pkg/types/spantypes"
"github.com/SigNoz/signoz/pkg/valuer"
)
type module struct {
store spantypes.Store
}
func NewModule(store spantypes.Store) spanmapper.Module {
return &module{store: store}
}
func (m *module) ListGroups(ctx context.Context, orgID valuer.UUID, q *spantypes.ListSpanMapperGroupsQuery) ([]*spantypes.SpanMapperGroup, error) {
storables, err := m.store.ListSpanMapperGroups(ctx, orgID, q)
if err != nil {
return nil, err
}
return spantypes.NewSpanMapperGroupsFromStorableGroups(storables), nil
}
func (m *module) GetGroup(ctx context.Context, orgID valuer.UUID, id valuer.UUID) (*spantypes.SpanMapperGroup, error) {
s, err := m.store.GetSpanMapperGroup(ctx, orgID, id)
if err != nil {
return nil, err
}
return spantypes.NewSpanMapperGroupFromStorable(s), nil
}
func (m *module) CreateGroup(ctx context.Context, orgID valuer.UUID, createdBy string, group *spantypes.SpanMapperGroup) error {
now := time.Now()
group.ID = valuer.GenerateUUID()
group.OrgID = orgID
group.CreatedAt = now
group.UpdatedAt = now
group.CreatedBy = createdBy
group.UpdatedBy = createdBy
storable := &spantypes.StorableSpanMapperGroup{
Identifiable: types.Identifiable{ID: group.ID},
TimeAuditable: types.TimeAuditable{CreatedAt: now, UpdatedAt: now},
UserAuditable: types.UserAuditable{CreatedBy: createdBy, UpdatedBy: createdBy},
OrgID: orgID,
Name: group.Name,
Category: group.Category,
Condition: group.Condition,
Enabled: group.Enabled,
}
if err := m.store.CreateSpanMapperGroup(ctx, storable); err != nil {
return err
}
agentConf.NotifyConfigUpdate(ctx)
return nil
}
func (m *module) UpdateGroup(ctx context.Context, orgID valuer.UUID, id valuer.UUID, updatedBy string, group *spantypes.SpanMapperGroup) error {
existing, err := m.store.GetSpanMapperGroup(ctx, orgID, id)
if err != nil {
return err
}
if group.Name != "" {
existing.Name = group.Name
}
if len(group.Condition.Attributes) > 0 || len(group.Condition.Resource) > 0 {
existing.Condition = group.Condition
}
existing.Enabled = group.Enabled
existing.UpdatedAt = time.Now()
existing.UpdatedBy = updatedBy
if err := m.store.UpdateSpanMapperGroup(ctx, existing); err != nil {
return err
}
agentConf.NotifyConfigUpdate(ctx)
return nil
}
func (m *module) DeleteGroup(ctx context.Context, orgID valuer.UUID, id valuer.UUID) error {
if err := m.store.DeleteSpanMapperGroup(ctx, orgID, id); err != nil {
return err
}
agentConf.NotifyConfigUpdate(ctx)
return nil
}
func (m *module) ListMappers(ctx context.Context, orgID valuer.UUID, groupID valuer.UUID) ([]*spantypes.SpanMapper, error) {
storables, err := m.store.ListSpanMappers(ctx, orgID, groupID)
if err != nil {
return nil, err
}
return spantypes.NewSpanMappersFromStorableSpanMappers(storables), nil
}
func (m *module) GetMapper(ctx context.Context, orgID valuer.UUID, groupID valuer.UUID, id valuer.UUID) (*spantypes.SpanMapper, error) {
s, err := m.store.GetSpanMapper(ctx, orgID, groupID, id)
if err != nil {
return nil, err
}
return spantypes.NewSpanMapperFromStorable(s), nil
}
func (m *module) CreateMapper(ctx context.Context, orgID valuer.UUID, groupID valuer.UUID, createdBy string, mapper *spantypes.SpanMapper) error {
// Ensure the group belongs to the org before inserting the child row.
if _, err := m.store.GetSpanMapperGroup(ctx, orgID, groupID); err != nil {
return err
}
now := time.Now()
mapper.ID = valuer.GenerateUUID()
mapper.GroupID = groupID
mapper.CreatedAt = now
mapper.UpdatedAt = now
mapper.CreatedBy = createdBy
mapper.UpdatedBy = createdBy
storable := &spantypes.StorableSpanMapper{
Identifiable: types.Identifiable{ID: mapper.ID},
TimeAuditable: types.TimeAuditable{CreatedAt: now, UpdatedAt: now},
UserAuditable: types.UserAuditable{CreatedBy: createdBy, UpdatedBy: createdBy},
GroupID: groupID,
Name: mapper.Name,
FieldContext: mapper.FieldContext,
Config: mapper.Config,
Enabled: mapper.Enabled,
}
if err := m.store.CreateSpanMapper(ctx, storable); err != nil {
return err
}
agentConf.NotifyConfigUpdate(ctx)
return nil
}
func (m *module) UpdateMapper(ctx context.Context, orgID valuer.UUID, groupID valuer.UUID, id valuer.UUID, updatedBy string, mapper *spantypes.SpanMapper) error {
existing, err := m.store.GetSpanMapper(ctx, orgID, groupID, id)
if err != nil {
return err
}
if mapper.FieldContext != (spantypes.FieldContext{}) {
existing.FieldContext = mapper.FieldContext
}
if mapper.Config.Sources != nil {
existing.Config = mapper.Config
}
existing.Enabled = mapper.Enabled
existing.UpdatedAt = time.Now()
existing.UpdatedBy = updatedBy
if err := m.store.UpdateSpanMapper(ctx, existing); err != nil {
return err
}
agentConf.NotifyConfigUpdate(ctx)
return nil
}
func (m *module) DeleteMapper(ctx context.Context, orgID valuer.UUID, groupID valuer.UUID, id valuer.UUID) error {
if err := m.store.DeleteSpanMapper(ctx, orgID, groupID, id); err != nil {
return err
}
agentConf.NotifyConfigUpdate(ctx)
return nil
}

View File

@@ -0,0 +1,236 @@
package implspanmapper
import (
"context"
"database/sql"
"github.com/SigNoz/signoz/pkg/errors"
"github.com/SigNoz/signoz/pkg/sqlstore"
"github.com/SigNoz/signoz/pkg/types/spantypes"
"github.com/SigNoz/signoz/pkg/valuer"
)
type store struct {
sqlstore sqlstore.SQLStore
}
func NewStore(sqlstore sqlstore.SQLStore) spantypes.Store {
return &store{sqlstore: sqlstore}
}
func (s *store) ListSpanMapperGroups(ctx context.Context, orgID valuer.UUID, q *spantypes.ListSpanMapperGroupsQuery) ([]*spantypes.StorableSpanMapperGroup, error) {
groups := make([]*spantypes.StorableSpanMapperGroup, 0)
sel := s.sqlstore.
BunDB().
NewSelect().
Model(&groups).
Where("org_id = ?", orgID)
if q != nil {
if q.Category != nil {
sel = sel.Where("category = ?", valuer.String(*q.Category).StringValue())
}
if q.Enabled != nil {
sel = sel.Where("enabled = ?", *q.Enabled)
}
}
if err := sel.Order("created_at DESC").Scan(ctx); err != nil {
return nil, err
}
return groups, nil
}
func (s *store) GetSpanMapperGroup(ctx context.Context, orgID, id valuer.UUID) (*spantypes.StorableSpanMapperGroup, error) {
group := new(spantypes.StorableSpanMapperGroup)
err := s.sqlstore.
BunDB().
NewSelect().
Model(group).
Where("org_id = ?", orgID).
Where("id = ?", id).
Scan(ctx)
if err != nil {
if err == sql.ErrNoRows {
return nil, s.sqlstore.WrapNotFoundErrf(err, spantypes.ErrCodeMappingGroupNotFound, "span mapper group %s not found", id)
}
return nil, err
}
return group, nil
}
func (s *store) CreateSpanMapperGroup(ctx context.Context, group *spantypes.StorableSpanMapperGroup) error {
_, err := s.sqlstore.
BunDBCtx(ctx).
NewInsert().
Model(group).
Exec(ctx)
if err != nil {
return s.sqlstore.WrapAlreadyExistsErrf(err, spantypes.ErrCodeMappingGroupAlreadyExists, "span mapper group %q already exists", group.Name)
}
return nil
}
func (s *store) UpdateSpanMapperGroup(ctx context.Context, group *spantypes.StorableSpanMapperGroup) error {
res, err := s.sqlstore.
BunDBCtx(ctx).
NewUpdate().
Model(group).
Where("org_id = ?", group.OrgID).
Where("id = ?", group.ID).
ExcludeColumn("id", "org_id", "created_at", "created_by").
Exec(ctx)
if err != nil {
return err
}
rowsAffected, err := res.RowsAffected()
if err != nil {
return err
}
if rowsAffected == 0 {
return errors.Newf(errors.TypeNotFound, spantypes.ErrCodeMappingGroupNotFound, "span mapper group %s not found", group.ID)
}
return nil
}
func (s *store) DeleteSpanMapperGroup(ctx context.Context, orgID, id valuer.UUID) error {
tx, err := s.sqlstore.BunDBCtx(ctx).BeginTx(ctx, nil)
if err != nil {
return err
}
defer func() { _ = tx.Rollback() }()
// Cascade: remove mappers belonging to this group first.
if _, err := tx.NewDelete().
Model((*spantypes.StorableSpanMapper)(nil)).
Where("group_id = ?", id).
Exec(ctx); err != nil {
return err
}
res, err := tx.NewDelete().
Model((*spantypes.StorableSpanMapperGroup)(nil)).
Where("org_id = ?", orgID).
Where("id = ?", id).
Exec(ctx)
if err != nil {
return err
}
rowsAffected, err := res.RowsAffected()
if err != nil {
return err
}
if rowsAffected == 0 {
return errors.Newf(errors.TypeNotFound, spantypes.ErrCodeMappingGroupNotFound, "span mapper group %s not found", id)
}
return tx.Commit()
}
func (s *store) ListSpanMappers(ctx context.Context, orgID, groupID valuer.UUID) ([]*spantypes.StorableSpanMapper, error) {
mappers := make([]*spantypes.StorableSpanMapper, 0)
// Scope by org via the parent group's org_id.
if _, err := s.GetSpanMapperGroup(ctx, orgID, groupID); err != nil {
return nil, err
}
if err := s.sqlstore.
BunDB().
NewSelect().
Model(&mappers).
Where("group_id = ?", groupID).
Order("created_at DESC").
Scan(ctx); err != nil {
return nil, err
}
return mappers, nil
}
func (s *store) GetSpanMapper(ctx context.Context, orgID, groupID, id valuer.UUID) (*spantypes.StorableSpanMapper, error) {
// Ensure the group belongs to the org.
if _, err := s.GetSpanMapperGroup(ctx, orgID, groupID); err != nil {
return nil, err
}
mapper := new(spantypes.StorableSpanMapper)
err := s.sqlstore.
BunDB().
NewSelect().
Model(mapper).
Where("group_id = ?", groupID).
Where("id = ?", id).
Scan(ctx)
if err != nil {
if err == sql.ErrNoRows {
return nil, s.sqlstore.WrapNotFoundErrf(err, spantypes.ErrCodeMapperNotFound, "span mapper %s not found", id)
}
return nil, err
}
return mapper, nil
}
func (s *store) CreateSpanMapper(ctx context.Context, mapper *spantypes.StorableSpanMapper) error {
_, err := s.sqlstore.
BunDBCtx(ctx).
NewInsert().
Model(mapper).
Exec(ctx)
if err != nil {
return s.sqlstore.WrapAlreadyExistsErrf(err, spantypes.ErrCodeMapperAlreadyExists, "span mapper %q already exists", mapper.Name)
}
return nil
}
func (s *store) UpdateSpanMapper(ctx context.Context, mapper *spantypes.StorableSpanMapper) error {
res, err := s.sqlstore.
BunDBCtx(ctx).
NewUpdate().
Model(mapper).
Where("group_id = ?", mapper.GroupID).
Where("id = ?", mapper.ID).
ExcludeColumn("id", "group_id", "created_at", "created_by").
Exec(ctx)
if err != nil {
return err
}
rowsAffected, err := res.RowsAffected()
if err != nil {
return err
}
if rowsAffected == 0 {
return errors.Newf(errors.TypeNotFound, spantypes.ErrCodeMapperNotFound, "span mapper %s not found", mapper.ID)
}
return nil
}
func (s *store) DeleteSpanMapper(ctx context.Context, orgID, groupID, id valuer.UUID) error {
if _, err := s.GetSpanMapperGroup(ctx, orgID, groupID); err != nil {
return err
}
res, err := s.sqlstore.
BunDBCtx(ctx).
NewDelete().
Model((*spantypes.StorableSpanMapper)(nil)).
Where("group_id = ?", groupID).
Where("id = ?", id).
Exec(ctx)
if err != nil {
return err
}
rowsAffected, err := res.RowsAffected()
if err != nil {
return err
}
if rowsAffected == 0 {
return errors.Newf(errors.TypeNotFound, spantypes.ErrCodeMapperNotFound, "span mapper %s not found", id)
}
return nil
}

View File

@@ -0,0 +1,41 @@
package spanmapper
import (
"context"
"net/http"
"github.com/SigNoz/signoz/pkg/types/spantypes"
"github.com/SigNoz/signoz/pkg/valuer"
)
// Module defines the business logic for span attribute mapping groups and mappers.
type Module interface {
// Group operations
ListGroups(ctx context.Context, orgID valuer.UUID, q *spantypes.ListSpanMapperGroupsQuery) ([]*spantypes.SpanMapperGroup, error)
GetGroup(ctx context.Context, orgID valuer.UUID, id valuer.UUID) (*spantypes.SpanMapperGroup, error)
CreateGroup(ctx context.Context, orgID valuer.UUID, createdBy string, group *spantypes.SpanMapperGroup) error
UpdateGroup(ctx context.Context, orgID valuer.UUID, id valuer.UUID, updatedBy string, group *spantypes.SpanMapperGroup) error
DeleteGroup(ctx context.Context, orgID valuer.UUID, id valuer.UUID) error
// Mapper operations
ListMappers(ctx context.Context, orgID valuer.UUID, groupID valuer.UUID) ([]*spantypes.SpanMapper, error)
GetMapper(ctx context.Context, orgID valuer.UUID, groupID valuer.UUID, id valuer.UUID) (*spantypes.SpanMapper, error)
CreateMapper(ctx context.Context, orgID valuer.UUID, groupID valuer.UUID, createdBy string, mapper *spantypes.SpanMapper) error
UpdateMapper(ctx context.Context, orgID valuer.UUID, groupID valuer.UUID, id valuer.UUID, updatedBy string, mapper *spantypes.SpanMapper) error
DeleteMapper(ctx context.Context, orgID valuer.UUID, groupID valuer.UUID, id valuer.UUID) error
}
// Handler defines the HTTP handler interface for mapping group and mapper endpoints.
type Handler interface {
// Group handlers
ListGroups(rw http.ResponseWriter, r *http.Request)
CreateGroup(rw http.ResponseWriter, r *http.Request)
UpdateGroup(rw http.ResponseWriter, r *http.Request)
DeleteGroup(rw http.ResponseWriter, r *http.Request)
// Mapper handlers
ListMappers(rw http.ResponseWriter, r *http.Request)
CreateMapper(rw http.ResponseWriter, r *http.Request)
UpdateMapper(rw http.ResponseWriter, r *http.Request)
DeleteMapper(rw http.ResponseWriter, r *http.Request)
}

View File

@@ -9,6 +9,7 @@ import (
"github.com/SigNoz/signoz/pkg/cache/memorycache"
"github.com/SigNoz/signoz/pkg/errors"
"github.com/SigNoz/signoz/pkg/modules/spanmapper/implspanmapper"
"github.com/SigNoz/signoz/pkg/queryparser"
"github.com/gorilla/handlers"
@@ -129,11 +130,14 @@ func NewServer(config signoz.Config, signoz *signoz.SigNoz) (*Server, error) {
opAmpModel.Init(signoz.SQLStore, signoz.Instrumentation.Logger(), signoz.Modules.OrgGetter)
spanAttrMappingFeature := implspanmapper.NewSpanAttrMappingFeature(signoz.Modules.SpanMapper)
agentConfMgr, err := agentConf.Initiate(
&agentConf.ManagerOptions{
Store: signoz.SQLStore,
AgentFeatures: []agentConf.AgentFeature{
logParsingPipelineController,
spanAttrMappingFeature,
},
},
)

View File

@@ -22,8 +22,6 @@ import (
"github.com/SigNoz/signoz/pkg/modules/fields/implfields"
"github.com/SigNoz/signoz/pkg/modules/inframonitoring"
"github.com/SigNoz/signoz/pkg/modules/inframonitoring/implinframonitoring"
"github.com/SigNoz/signoz/pkg/modules/llmpricingrule"
"github.com/SigNoz/signoz/pkg/modules/llmpricingrule/impllmpricingrule"
"github.com/SigNoz/signoz/pkg/modules/metricsexplorer"
"github.com/SigNoz/signoz/pkg/modules/metricsexplorer/implmetricsexplorer"
"github.com/SigNoz/signoz/pkg/modules/quickfilter"
@@ -38,6 +36,8 @@ import (
"github.com/SigNoz/signoz/pkg/modules/serviceaccount/implserviceaccount"
"github.com/SigNoz/signoz/pkg/modules/services"
"github.com/SigNoz/signoz/pkg/modules/services/implservices"
"github.com/SigNoz/signoz/pkg/modules/spanmapper"
"github.com/SigNoz/signoz/pkg/modules/spanmapper/implspanmapper"
"github.com/SigNoz/signoz/pkg/modules/spanpercentile"
"github.com/SigNoz/signoz/pkg/modules/spanpercentile/implspanpercentile"
"github.com/SigNoz/signoz/pkg/modules/tracedetail"
@@ -73,10 +73,10 @@ type Handlers struct {
RegistryHandler factory.Handler
CloudIntegrationHandler cloudintegration.Handler
RuleStateHistory rulestatehistory.Handler
SpanMapperHandler spanmapper.Handler
AlertmanagerHandler alertmanager.Handler
TraceDetail tracedetail.Handler
RulerHandler ruler.Handler
LLMPricingRuleHandler llmpricingrule.Handler
}
func NewHandlers(
@@ -117,9 +117,9 @@ func NewHandlers(
RegistryHandler: registryHandler,
RuleStateHistory: implrulestatehistory.NewHandler(modules.RuleStateHistory),
CloudIntegrationHandler: implcloudintegration.NewHandler(modules.CloudIntegration),
SpanMapperHandler: implspanmapper.NewHandler(modules.SpanMapper, providerSettings),
AlertmanagerHandler: signozalertmanager.NewHandler(alertmanagerService),
TraceDetail: impltracedetail.NewHandler(modules.TraceDetail),
RulerHandler: signozruler.NewHandler(rulerService),
LLMPricingRuleHandler: impllmpricingrule.NewHandler(nil, providerSettings),
}
}

View File

@@ -37,6 +37,8 @@ import (
"github.com/SigNoz/signoz/pkg/modules/services/implservices"
"github.com/SigNoz/signoz/pkg/modules/session"
"github.com/SigNoz/signoz/pkg/modules/session/implsession"
"github.com/SigNoz/signoz/pkg/modules/spanmapper"
"github.com/SigNoz/signoz/pkg/modules/spanmapper/implspanmapper"
"github.com/SigNoz/signoz/pkg/modules/spanpercentile"
"github.com/SigNoz/signoz/pkg/modules/spanpercentile/implspanpercentile"
"github.com/SigNoz/signoz/pkg/modules/tracedetail"
@@ -79,6 +81,7 @@ type Modules struct {
CloudIntegration cloudintegration.Module
RuleStateHistory rulestatehistory.Module
TraceDetail tracedetail.Module
SpanMapper spanmapper.Module
}
func NewModules(
@@ -131,5 +134,6 @@ func NewModules(
RuleStateHistory: implrulestatehistory.NewModule(implrulestatehistory.NewStore(telemetryStore, telemetryMetadataStore, providerSettings.Logger)),
CloudIntegration: cloudIntegrationModule,
TraceDetail: impltracedetail.NewModule(impltracedetail.NewTraceStore(telemetryStore), providerSettings, config.TraceDetail),
SpanMapper: implspanmapper.NewModule(implspanmapper.NewStore(sqlstore)),
}
}

View File

@@ -22,7 +22,6 @@ import (
"github.com/SigNoz/signoz/pkg/modules/dashboard"
"github.com/SigNoz/signoz/pkg/modules/fields"
"github.com/SigNoz/signoz/pkg/modules/inframonitoring"
"github.com/SigNoz/signoz/pkg/modules/llmpricingrule"
"github.com/SigNoz/signoz/pkg/modules/metricsexplorer"
"github.com/SigNoz/signoz/pkg/modules/organization"
"github.com/SigNoz/signoz/pkg/modules/preference"
@@ -31,6 +30,7 @@ import (
"github.com/SigNoz/signoz/pkg/modules/rulestatehistory"
"github.com/SigNoz/signoz/pkg/modules/serviceaccount"
"github.com/SigNoz/signoz/pkg/modules/session"
"github.com/SigNoz/signoz/pkg/modules/spanmapper"
"github.com/SigNoz/signoz/pkg/modules/tracedetail"
"github.com/SigNoz/signoz/pkg/modules/user"
"github.com/SigNoz/signoz/pkg/querier"
@@ -75,8 +75,8 @@ func NewOpenAPI(ctx context.Context, instrumentation instrumentation.Instrumenta
struct{ factory.Handler }{},
struct{ cloudintegration.Handler }{},
struct{ rulestatehistory.Handler }{},
struct{ spanmapper.Handler }{},
struct{ alertmanager.Handler }{},
struct{ llmpricingrule.Handler }{},
struct{ tracedetail.Handler }{},
struct{ ruler.Handler }{},
).New(ctx, instrumentation.ToProviderSettings(), apiserver.Config{})

View File

@@ -195,6 +195,7 @@ func NewSQLMigrationProviderFactories(
sqlmigration.NewServiceAccountAuthzactory(sqlstore),
sqlmigration.NewDropUserDeletedAtFactory(sqlstore, sqlschema),
sqlmigration.NewMigrateAWSAllRegionsFactory(sqlstore),
sqlmigration.NewAddSpanMapperFactory(sqlstore, sqlschema),
)
}
@@ -281,8 +282,8 @@ func NewAPIServerProviderFactories(orgGetter organization.Getter, authz authz.Au
handlers.RegistryHandler,
handlers.CloudIntegrationHandler,
handlers.RuleStateHistory,
handlers.SpanMapperHandler,
handlers.AlertmanagerHandler,
handlers.LLMPricingRuleHandler,
handlers.TraceDetail,
handlers.RulerHandler,
),

View File

@@ -0,0 +1,119 @@
package sqlmigration
import (
"context"
"github.com/SigNoz/signoz/pkg/factory"
"github.com/SigNoz/signoz/pkg/sqlschema"
"github.com/SigNoz/signoz/pkg/sqlstore"
"github.com/uptrace/bun"
"github.com/uptrace/bun/migrate"
)
type addSpanMapper struct {
sqlschema sqlschema.SQLSchema
sqlstore sqlstore.SQLStore
}
func NewAddSpanMapperFactory(sqlstore sqlstore.SQLStore, sqlschema sqlschema.SQLSchema) factory.ProviderFactory[SQLMigration, Config] {
return factory.NewProviderFactory(factory.MustNewName("add_span_mapper"), func(_ context.Context, _ factory.ProviderSettings, _ Config) (SQLMigration, error) {
return &addSpanMapper{sqlschema: sqlschema, sqlstore: sqlstore}, nil
})
}
func (migration *addSpanMapper) Register(migrations *migrate.Migrations) error {
return migrations.Register(migration.Up, migration.Down)
}
func (migration *addSpanMapper) Up(ctx context.Context, db *bun.DB) error {
tx, err := db.BeginTx(ctx, nil)
if err != nil {
return err
}
defer func() {
_ = tx.Rollback()
}()
sqls := [][]byte{}
groupSQLs := migration.sqlschema.Operator().CreateTable(&sqlschema.Table{
Name: "span_mapper_group",
Columns: []*sqlschema.Column{
{Name: "id", DataType: sqlschema.DataTypeText, Nullable: false},
{Name: "created_at", DataType: sqlschema.DataTypeTimestamp, Nullable: false},
{Name: "updated_at", DataType: sqlschema.DataTypeTimestamp, Nullable: false},
{Name: "created_by", DataType: sqlschema.DataTypeText, Nullable: false},
{Name: "updated_by", DataType: sqlschema.DataTypeText, Nullable: false},
{Name: "org_id", DataType: sqlschema.DataTypeText, Nullable: false},
{Name: "name", DataType: sqlschema.DataTypeText, Nullable: false},
{Name: "category", DataType: sqlschema.DataTypeText, Nullable: false},
{Name: "condition", DataType: sqlschema.DataTypeText, Nullable: false},
{Name: "enabled", DataType: sqlschema.DataTypeBoolean, Nullable: false, Default: "true"},
},
PrimaryKeyConstraint: &sqlschema.PrimaryKeyConstraint{
ColumnNames: []sqlschema.ColumnName{"id"},
},
ForeignKeyConstraints: []*sqlschema.ForeignKeyConstraint{
{
ReferencingColumnName: sqlschema.ColumnName("org_id"),
ReferencedTableName: sqlschema.TableName("organizations"),
ReferencedColumnName: sqlschema.ColumnName("id"),
},
},
})
sqls = append(sqls, groupSQLs...)
groupIdxSQLs := migration.sqlschema.Operator().CreateIndex(
&sqlschema.UniqueIndex{
TableName: "span_mapper_group",
ColumnNames: []sqlschema.ColumnName{"org_id", "name"},
})
sqls = append(sqls, groupIdxSQLs...)
mapperSQLs := migration.sqlschema.Operator().CreateTable(&sqlschema.Table{
Name: "span_mapper",
Columns: []*sqlschema.Column{
{Name: "id", DataType: sqlschema.DataTypeText, Nullable: false},
{Name: "created_at", DataType: sqlschema.DataTypeTimestamp, Nullable: false},
{Name: "updated_at", DataType: sqlschema.DataTypeTimestamp, Nullable: false},
{Name: "created_by", DataType: sqlschema.DataTypeText, Nullable: false},
{Name: "updated_by", DataType: sqlschema.DataTypeText, Nullable: false},
{Name: "group_id", DataType: sqlschema.DataTypeText, Nullable: false},
{Name: "name", DataType: sqlschema.DataTypeText, Nullable: false},
{Name: "field_context", DataType: sqlschema.DataTypeText, Nullable: false},
{Name: "config", DataType: sqlschema.DataTypeText, Nullable: false},
{Name: "enabled", DataType: sqlschema.DataTypeBoolean, Nullable: false, Default: "true"},
},
PrimaryKeyConstraint: &sqlschema.PrimaryKeyConstraint{
ColumnNames: []sqlschema.ColumnName{"id"},
},
ForeignKeyConstraints: []*sqlschema.ForeignKeyConstraint{
{
ReferencingColumnName: sqlschema.ColumnName("group_id"),
ReferencedTableName: sqlschema.TableName("span_mapper_group"),
ReferencedColumnName: sqlschema.ColumnName("id"),
},
},
})
sqls = append(sqls, mapperSQLs...)
mapperIdxSQLs := migration.sqlschema.Operator().CreateIndex(
&sqlschema.UniqueIndex{
TableName: "span_mapper",
ColumnNames: []sqlschema.ColumnName{"group_id", "name"},
})
sqls = append(sqls, mapperIdxSQLs...)
for _, sql := range sqls {
if _, err := tx.ExecContext(ctx, string(sql)); err != nil {
return err
}
}
return tx.Commit()
}
func (migration *addSpanMapper) Down(context.Context, *bun.DB) error {
return nil
}

View File

@@ -1,144 +0,0 @@
package llmpricingruletypes
import (
"time"
"github.com/SigNoz/signoz/pkg/errors"
"github.com/SigNoz/signoz/pkg/types"
"github.com/SigNoz/signoz/pkg/valuer"
)
var (
ErrCodePricingRuleNotFound = errors.MustNewCode("pricing_rule_not_found")
ErrCodePricingRuleInvalidInput = errors.MustNewCode("pricing_rule_invalid_input")
)
type LLMPricingRuleUnit struct {
valuer.String
}
var (
UnitPerMillionTokens = LLMPricingRuleUnit{valuer.NewString("per_million_tokens")}
)
type LLMPricingRuleCacheMode struct {
valuer.String
}
var (
// LLMPricingRuleCacheModeSubtract: cached tokens are inside input_tokens (OpenAI-style).
LLMPricingRuleCacheModeSubtract = LLMPricingRuleCacheMode{valuer.NewString("subtract")}
// LLMPricingRuleCacheModeAdditive: cached tokens are reported separately (Anthropic-style).
LLMPricingRuleCacheModeAdditive = LLMPricingRuleCacheMode{valuer.NewString("additive")}
// LLMPricingRuleCacheModeUnknown: provider behaviour is unknown; falls back to subtract.
LLMPricingRuleCacheModeUnknown = LLMPricingRuleCacheMode{valuer.NewString("unknown")}
)
// LLMPricingRule is the domain model for an LLM pricing rule.
// It also doubles as the HTTP response shape; see GettablePricingRule.
type LLMPricingRule struct {
types.TimeAuditable
types.UserAuditable
ID valuer.UUID `json:"id" required:"true"`
OrgID valuer.UUID `json:"orgId" required:"true"`
SourceID *valuer.UUID `json:"sourceId,omitempty"`
Model string `json:"modelName" required:"true"`
ModelPattern []string `json:"modelPattern" required:"true"`
Unit LLMPricingRuleUnit `json:"unit" required:"true"`
CacheMode LLMPricingRuleCacheMode `json:"cacheMode" required:"true"`
CostInput float64 `json:"costInput" required:"true"`
CostOutput float64 `json:"costOutput" required:"true"`
CostCacheRead float64 `json:"costCacheRead" required:"true"`
CostCacheWrite float64 `json:"costCacheWrite" required:"true"`
IsOverride bool `json:"isOverride" required:"true"`
SyncedAt *time.Time `json:"syncedAt,omitempty"`
Enabled bool `json:"enabled" required:"true"`
}
// GettablePricingRule is a type alias for PricingRule — the response shape is
// identical to the core type, so per pkg/types conventions we do not mint a
// separate flavor.
type GettableLLMPricingRule = LLMPricingRule
// UpdatablePricingRule is one entry in the bulk upsert batch.
//
// Identification:
// - ID set → match by id (user editing a known row).
// - SourceID set → match by source_id (Zeus sync, or user editing a Zeus-synced row).
// - neither set → insert a new row with source_id = NULL (user-created custom rule).
//
// IsOverride is a pointer so the caller can distinguish "not sent" from "set to false".
// When IsOverride is nil AND the matched row has is_override = true, the row is fully
// preserved — only synced_at is stamped.
type UpdatableLLMPricingRule struct {
ID *valuer.UUID `json:"id,omitempty"`
SourceID *valuer.UUID `json:"sourceId,omitempty"`
Model string `json:"modelName" required:"true"`
ModelPattern []string `json:"modelPattern" required:"true"`
Unit LLMPricingRuleUnit `json:"unit" required:"true"`
CacheMode LLMPricingRuleCacheMode `json:"cacheMode" required:"true"`
CostInput float64 `json:"costInput" required:"true"`
CostOutput float64 `json:"costOutput" required:"true"`
CostCacheRead float64 `json:"costCacheRead" required:"true"`
CostCacheWrite float64 `json:"costCacheWrite" required:"true"`
IsOverride *bool `json:"isOverride,omitempty"`
Enabled bool `json:"enabled" required:"true"`
}
type UpdatableLLMPricingRules struct {
Rules []UpdatableLLMPricingRule `json:"rules" required:"true"`
}
type ListPricingRulesQuery struct {
Offset int `query:"offset" json:"offset"`
Limit int `query:"limit" json:"limit"`
}
type GettablePricingRules struct {
Items []*GettableLLMPricingRule `json:"items" required:"true"`
Total int `json:"total" required:"true"`
Offset int `json:"offset" required:"true"`
Limit int `json:"limit" required:"true"`
}
func (LLMPricingRuleUnit) Enum() []any {
return []any{UnitPerMillionTokens}
}
func (LLMPricingRuleCacheMode) Enum() []any {
return []any{LLMPricingRuleCacheModeSubtract, LLMPricingRuleCacheModeAdditive, LLMPricingRuleCacheModeUnknown}
}
func NewLLMPricingRuleFromStorable(s *StorableLLMPricingRule) *LLMPricingRule {
pattern := make([]string, len(s.ModelPattern))
copy(pattern, s.ModelPattern)
return &LLMPricingRule{
TimeAuditable: s.TimeAuditable,
UserAuditable: s.UserAuditable,
ID: s.ID,
OrgID: s.OrgID,
SourceID: s.SourceID,
Model: s.Model,
ModelPattern: pattern,
Unit: s.Unit,
CacheMode: s.CacheMode,
CostInput: s.CostInput,
CostOutput: s.CostOutput,
CostCacheRead: s.CostCacheRead,
CostCacheWrite: s.CostCacheWrite,
IsOverride: s.IsOverride,
SyncedAt: s.SyncedAt,
Enabled: s.Enabled,
}
}
func NewGettableLLMPricingRulesFromLLMPricingRules(items []*LLMPricingRule, total, offset, limit int) *GettablePricingRules {
return &GettablePricingRules{
Items: items,
Total: total,
Offset: offset,
Limit: limit,
}
}

View File

@@ -1,67 +0,0 @@
package llmpricingruletypes
import (
"database/sql/driver"
"encoding/json"
"time"
"github.com/SigNoz/signoz/pkg/errors"
"github.com/SigNoz/signoz/pkg/types"
"github.com/SigNoz/signoz/pkg/valuer"
"github.com/uptrace/bun"
)
// StringSlice is a []string that is stored as a JSON text column.
// It is compatible with both SQLite and PostgreSQL.
type StringSlice []string
// StorableLLMPricingRule is the bun/DB representation of an LLM pricing rule.
type StorableLLMPricingRule struct {
bun.BaseModel `bun:"table:llm_pricing_rule,alias:llm_pricing_rule"`
types.Identifiable
types.TimeAuditable
types.UserAuditable
OrgID valuer.UUID `bun:"org_id,type:text,notnull"`
SourceID *valuer.UUID `bun:"source_id,type:text"`
Model string `bun:"model,type:text,notnull"`
ModelPattern StringSlice `bun:"model_pattern,type:text,notnull"`
Unit LLMPricingRuleUnit `bun:"unit,type:text,notnull"`
CacheMode LLMPricingRuleCacheMode `bun:"cache_mode,type:text,notnull"`
CostInput float64 `bun:"cost_input,notnull"`
CostOutput float64 `bun:"cost_output,notnull"`
CostCacheRead float64 `bun:"cost_cache_read,notnull"`
CostCacheWrite float64 `bun:"cost_cache_write,notnull"`
// IsOverride marks the row as user-pinned. When true, Zeus skips it entirely.
IsOverride bool `bun:"is_override,notnull,default:false"`
SyncedAt *time.Time `bun:"synced_at"`
Enabled bool `bun:"enabled,notnull,default:true"`
}
func (s StringSlice) Value() (driver.Value, error) {
if s == nil {
return "[]", nil
}
b, err := json.Marshal(s)
if err != nil {
return nil, err
}
return string(b), nil
}
func (s *StringSlice) Scan(src any) error {
var raw []byte
switch v := src.(type) {
case string:
raw = []byte(v)
case []byte:
raw = v
case nil:
*s = nil
return nil
default:
return errors.NewInternalf(errors.CodeInternal, "llmpricingruletypes: cannot scan %T into StringSlice", src)
}
return json.Unmarshal(raw, s)
}

View File

@@ -1,16 +0,0 @@
package llmpricingruletypes
import (
"context"
"github.com/SigNoz/signoz/pkg/valuer"
)
type Store interface {
List(ctx context.Context, orgID valuer.UUID, offset, limit int) ([]*StorableLLMPricingRule, int, error)
Get(ctx context.Context, orgID, id valuer.UUID) (*StorableLLMPricingRule, error)
GetBySourceID(ctx context.Context, orgID, sourceID valuer.UUID) (*StorableLLMPricingRule, error)
Create(ctx context.Context, rule *StorableLLMPricingRule) error
Update(ctx context.Context, rule *StorableLLMPricingRule) error
Delete(ctx context.Context, orgID, id valuer.UUID) error
}

View File

@@ -0,0 +1,137 @@
package spantypes
import (
"github.com/SigNoz/signoz/pkg/errors"
"github.com/SigNoz/signoz/pkg/types"
"github.com/SigNoz/signoz/pkg/valuer"
)
var (
ErrCodeMapperNotFound = errors.MustNewCode("span_attribute_mapper_not_found")
ErrCodeMapperAlreadyExists = errors.MustNewCode("span_attribute_mapper_already_exists")
ErrCodeMappingInvalidInput = errors.MustNewCode("span_attribute_mapping_invalid_input")
)
// FieldContext is where the target attribute is written.
type FieldContext struct {
valuer.String
}
var (
FieldContextSpanAttribute = FieldContext{valuer.NewString("attribute")}
FieldContextResource = FieldContext{valuer.NewString("resource")}
)
// MapperOperation determines whether the source attribute is moved (deleted) or copied.
type SpanMapperOperation struct {
valuer.String
}
var (
SpanMapperOperationMove = SpanMapperOperation{valuer.NewString("move")}
SpanMapperOperationCopy = SpanMapperOperation{valuer.NewString("copy")}
)
// MapperSource describes one candidate source for a target attribute.
type SpanMapperSource struct {
Key string `json:"key" required:"true"`
Context FieldContext `json:"context" required:"true"`
Operation SpanMapperOperation `json:"operation" required:"true"`
Priority int `json:"priority" required:"true"`
}
// MapperConfig holds the mapping logic for a single target attribute.
// It implements driver.Valuer and sql.Scanner for JSON text column storage.
type SpanMapperConfig struct {
Sources []SpanMapperSource `json:"sources" required:"true" nullable:"true"`
}
// SpanMapper is the domain model for a span attribute mapper.
type SpanMapper struct {
types.TimeAuditable
types.UserAuditable
ID valuer.UUID `json:"id" required:"true"`
GroupID valuer.UUID `json:"group_id" required:"true"`
Name string `json:"name" required:"true"`
FieldContext FieldContext `json:"field_context" required:"true"`
Config SpanMapperConfig `json:"config" required:"true"`
Enabled bool `json:"enabled" required:"true"`
}
type PostableSpanMapper struct {
Name string `json:"name" required:"true"`
FieldContext FieldContext `json:"field_context" required:"true"`
Config SpanMapperConfig `json:"config" required:"true"`
Enabled bool `json:"enabled"`
}
// UpdatableSpanMapper is the HTTP request body for updating a span mapper.
// All fields are optional; only non-nil fields are applied.
type UpdatableSpanMapper struct {
FieldContext FieldContext `json:"field_context,omitempty"`
Config *SpanMapperConfig `json:"config,omitempty"`
Enabled *bool `json:"enabled,omitempty"`
}
type GettableSpanMapper = SpanMapper
type GettableSpanMappers struct {
Items []*GettableSpanMapper `json:"items" required:"true" nullable:"false"`
}
func (FieldContext) Enum() []any {
return []any{FieldContextSpanAttribute, FieldContextResource}
}
func (SpanMapperOperation) Enum() []any {
return []any{SpanMapperOperationMove, SpanMapperOperationCopy}
}
func NewSpanMapperFromStorable(s *StorableSpanMapper) *SpanMapper {
return &SpanMapper{
TimeAuditable: s.TimeAuditable,
UserAuditable: s.UserAuditable,
ID: s.ID,
GroupID: s.GroupID,
Name: s.Name,
FieldContext: s.FieldContext,
Config: s.Config,
Enabled: s.Enabled,
}
}
func NewSpanMapperFromPostable(req *PostableSpanMapper) *SpanMapper {
return &SpanMapper{
Name: req.Name,
FieldContext: req.FieldContext,
Config: req.Config,
Enabled: req.Enabled,
}
}
func NewSpanMapperFromUpdatable(req *UpdatableSpanMapper) *SpanMapper {
m := &SpanMapper{}
if req.FieldContext != (FieldContext{}) {
m.FieldContext = req.FieldContext
}
if req.Config != nil {
m.Config = *req.Config
}
if req.Enabled != nil {
m.Enabled = *req.Enabled
}
return m
}
func NewSpanMappersFromStorableSpanMappers(ss []*StorableSpanMapper) []*SpanMapper {
mappers := make([]*SpanMapper, len(ss))
for i, s := range ss {
mappers[i] = NewSpanMapperFromStorable(s)
}
return mappers
}
func NewGettableSpanMappers(m []*SpanMapper) *GettableSpanMappers {
return &GettableSpanMappers{Items: m}
}

View File

@@ -0,0 +1,109 @@
package spantypes
import (
"github.com/SigNoz/signoz/pkg/errors"
"github.com/SigNoz/signoz/pkg/types"
"github.com/SigNoz/signoz/pkg/valuer"
)
var (
ErrCodeMappingGroupNotFound = errors.MustNewCode("span_attribute_mapping_group_not_found")
ErrCodeMappingGroupAlreadyExists = errors.MustNewCode("span_attribute_mapping_group_already_exists")
)
// SpanMapperGroupCategory defaults will be llm, tool, agent but user can configure more as they want.
type SpanMapperGroupCategory valuer.String
// A group runs when any of the listed attribute/resource key patterns match.
type SpanMapperGroupCondition struct {
Attributes []string `json:"attributes" required:"true" nullable:"true"`
Resource []string `json:"resource" required:"true" nullable:"true"`
}
// SpanMapperGroup is the domain model for a span attribute mapping group.
type SpanMapperGroup struct {
types.TimeAuditable
types.UserAuditable
ID valuer.UUID `json:"id" required:"true"`
OrgID valuer.UUID `json:"orgId" required:"true"`
Name string `json:"name" required:"true"`
Category SpanMapperGroupCategory `json:"category" required:"true"`
Condition SpanMapperGroupCondition `json:"condition" required:"true"`
Enabled bool `json:"enabled" required:"true"`
}
// GettableSpanMapperGroup is the HTTP response representation of a mapping group.
type GettableSpanMapperGroup = SpanMapperGroup
type PostableSpanMapperGroup struct {
Name string `json:"name" required:"true"`
Category SpanMapperGroupCategory `json:"category" required:"true"`
Condition SpanMapperGroupCondition `json:"condition" required:"true"`
Enabled bool `json:"enabled"`
}
// UpdatableSpanMapperGroup is the HTTP request body for updating a mapping group.
// All fields are optional; only non-nil fields are applied.
type UpdatableSpanMapperGroup struct {
Name *string `json:"name,omitempty"`
Condition *SpanMapperGroupCondition `json:"condition,omitempty"`
Enabled *bool `json:"enabled,omitempty"`
}
type ListSpanMapperGroupsQuery struct {
Category *SpanMapperGroupCategory `query:"category"`
Enabled *bool `query:"enabled"`
}
type GettableSpanMapperGroups struct {
Items []*GettableSpanMapperGroup `json:"items" required:"true" nullable:"false"`
}
func NewSpanMapperGroupFromStorable(s *StorableSpanMapperGroup) *SpanMapperGroup {
return &SpanMapperGroup{
TimeAuditable: s.TimeAuditable,
UserAuditable: s.UserAuditable,
ID: s.ID,
OrgID: s.OrgID,
Name: s.Name,
Category: s.Category,
Condition: s.Condition,
Enabled: s.Enabled,
}
}
func NewSpanMapperGroupFromPostable(p *PostableSpanMapperGroup) *SpanMapperGroup {
return &SpanMapperGroup{
Name: p.Name,
Category: p.Category,
Condition: p.Condition,
Enabled: p.Enabled,
}
}
func NewSpanMapperGroupFromUpdatable(u *UpdatableSpanMapperGroup) *SpanMapperGroup {
g := &SpanMapperGroup{}
if u.Name != nil {
g.Name = *u.Name
}
if u.Condition != nil {
g.Condition = *u.Condition
}
if u.Enabled != nil {
g.Enabled = *u.Enabled
}
return g
}
func NewSpanMapperGroupsFromStorableGroups(ss []*StorableSpanMapperGroup) []*SpanMapperGroup {
groups := make([]*SpanMapperGroup, len(ss))
for i, s := range ss {
groups[i] = NewSpanMapperGroupFromStorable(s)
}
return groups
}
func NewGettableSpanMapperGroups(g []*SpanMapperGroup) *GettableSpanMapperGroups {
return &GettableSpanMapperGroups{Items: g}
}

View File

@@ -0,0 +1,23 @@
package spantypes
type SpanMappingProcessorConfig struct {
Groups []SpanMappingGroup `yaml:"groups" json:"groups"`
}
type SpanMappingGroup struct {
ID string `yaml:"id" json:"id"`
ExistsAny SpanMappingExistsAny `yaml:"exists_any" json:"exists_any"`
Attributes []SpanMappingAttribute `yaml:"attributes" json:"attributes"`
}
type SpanMappingExistsAny struct {
Attributes []string `yaml:"attributes,omitempty" json:"attributes,omitempty"`
Resource []string `yaml:"resource,omitempty" json:"resource,omitempty"`
}
type SpanMappingAttribute struct {
Target string `yaml:"target" json:"target"`
Context string `yaml:"context,omitempty" json:"context,omitempty"`
Action string `yaml:"action,omitempty" json:"action,omitempty"`
Sources []string `yaml:"sources" json:"sources"`
}

View File

@@ -0,0 +1,87 @@
package spantypes
import (
"database/sql/driver"
"encoding/json"
"github.com/SigNoz/signoz/pkg/errors"
"github.com/SigNoz/signoz/pkg/types"
"github.com/SigNoz/signoz/pkg/valuer"
"github.com/uptrace/bun"
)
type StorableSpanMapperGroup struct {
bun.BaseModel `bun:"table:span_mapper_group,alias:span_attribute_mapping_group"`
types.Identifiable
types.TimeAuditable
types.UserAuditable
OrgID valuer.UUID `bun:"org_id,type:text,notnull"`
Name string `bun:"name,type:text,notnull"`
Category SpanMapperGroupCategory `bun:"category,type:text,notnull"`
Condition SpanMapperGroupCondition `bun:"condition,type:jsonb,notnull"`
Enabled bool `bun:"enabled,notnull,default:true"`
}
type StorableSpanMapper struct {
bun.BaseModel `bun:"table:span_mapper,alias:span_attribute_mapping"`
types.Identifiable
types.TimeAuditable
types.UserAuditable
GroupID valuer.UUID `bun:"group_id,type:text,notnull"`
Name string `bun:"name,type:text,notnull"`
FieldContext FieldContext `bun:"field_context,type:text,notnull"`
Config SpanMapperConfig `bun:"config,type:jsonb,notnull"`
Enabled bool `bun:"enabled,notnull,default:true"`
}
func (c SpanMapperGroupCondition) Value() (driver.Value, error) {
b, err := json.Marshal(c)
if err != nil {
return nil, err
}
return string(b), nil
}
func (c *SpanMapperGroupCondition) Scan(src any) error {
var raw []byte
switch v := src.(type) {
case string:
raw = []byte(v)
case []byte:
raw = v
case nil:
*c = SpanMapperGroupCondition{}
return nil
default:
return errors.NewInternalf(errors.CodeInternal, "spanmapper: cannot scan %T into Condition", src)
}
return json.Unmarshal(raw, c)
}
func (m SpanMapperConfig) Value() (driver.Value, error) {
b, err := json.Marshal(m)
if err != nil {
return nil, err
}
return string(b), nil
}
func (m *SpanMapperConfig) Scan(src any) error {
var raw []byte
switch v := src.(type) {
case string:
raw = []byte(v)
case []byte:
raw = v
case nil:
*m = SpanMapperConfig{}
return nil
default:
return errors.NewInternalf(errors.CodeInternal, "spanmapper: cannot scan %T into MapperConfig", src)
}
return json.Unmarshal(raw, m)
}

View File

@@ -0,0 +1,23 @@
package spantypes
import (
"context"
"github.com/SigNoz/signoz/pkg/valuer"
)
type Store interface {
// Group operations
ListSpanMapperGroups(ctx context.Context, orgID valuer.UUID, q *ListSpanMapperGroupsQuery) ([]*StorableSpanMapperGroup, error)
GetSpanMapperGroup(ctx context.Context, orgID, id valuer.UUID) (*StorableSpanMapperGroup, error)
CreateSpanMapperGroup(ctx context.Context, group *StorableSpanMapperGroup) error
UpdateSpanMapperGroup(ctx context.Context, group *StorableSpanMapperGroup) error
DeleteSpanMapperGroup(ctx context.Context, orgID, id valuer.UUID) error
// Mapper operations
ListSpanMappers(ctx context.Context, orgID, groupID valuer.UUID) ([]*StorableSpanMapper, error)
GetSpanMapper(ctx context.Context, orgID, groupID, id valuer.UUID) (*StorableSpanMapper, error)
CreateSpanMapper(ctx context.Context, mapper *StorableSpanMapper) error
UpdateSpanMapper(ctx context.Context, mapper *StorableSpanMapper) error
DeleteSpanMapper(ctx context.Context, orgID, groupID, id valuer.UUID) error
}