Compare commits

..

1 Commits

Author SHA1 Message Date
Piyush Singariya
ea2663b145 fix: enrich unspecified fields in logs pipelines filters (#10686)
Some checks are pending
build-staging / prepare (push) Waiting to run
build-staging / js-build (push) Blocked by required conditions
build-staging / go-build (push) Blocked by required conditions
build-staging / staging (push) Blocked by required conditions
Release Drafter / update_release_draft (push) Waiting to run
* fix: enrich unspecified fields

* fix: return error in enrich function

* chore: nit change asked
2026-03-25 05:01:26 +00:00
6 changed files with 109 additions and 85 deletions

View File

@@ -136,6 +136,7 @@ func NewServer(config signoz.Config, signoz *signoz.SigNoz) (*Server, error) {
logParsingPipelineController, err := logparsingpipeline.NewLogParsingPipelinesController(
signoz.SQLStore,
integrationsController.GetPipelinesForInstalledIntegrations,
reader,
)
if err != nil {
return nil, err

View File

@@ -12,8 +12,6 @@ function MetricsSearch({
currentQueryFilterExpression,
setCurrentQueryFilterExpression,
isLoading,
handleCancelQuery,
onRunQuery,
}: MetricsSearchProps): JSX.Element {
const handleOnChange = useCallback(
(expression: string): void => {
@@ -24,8 +22,7 @@ function MetricsSearch({
const handleStageAndRunQuery = useCallback(() => {
onChange(currentQueryFilterExpression);
onRunQuery?.();
}, [currentQueryFilterExpression, onChange, onRunQuery]);
}, [currentQueryFilterExpression, onChange]);
const handleRunQuery = useCallback(
(expression: string): void => {
@@ -56,7 +53,6 @@ function MetricsSearch({
<RunQueryBtn
onStageRunQuery={handleStageAndRunQuery}
isLoadingQueries={isLoading}
handleCancelQuery={handleCancelQuery}
/>
<div className="metrics-search-options">
<DateTimeSelectionV2

View File

@@ -3,7 +3,6 @@ import { useCallback, useEffect, useMemo, useState } from 'react';
import { useSelector } from 'react-redux';
import { useSearchParams } from 'react-router-dom-v5-compat';
import * as Sentry from '@sentry/react';
import { Typography } from 'antd';
import logEvent from 'api/common/logEvent';
import { convertToApiError } from 'api/ErrorResponseHandlerForGeneratedAPIs';
import {
@@ -102,8 +101,6 @@ function Summary(): JSX.Element {
setCurrentQueryFilterExpression,
] = useState<string>(appliedFilterExpression);
const [isCancelled, setIsCancelled] = useState<boolean>(false);
useEffect(() => {
setCurrentQueryFilterExpression(appliedFilterExpression);
}, [appliedFilterExpression]);
@@ -164,7 +161,6 @@ function Summary(): JSX.Element {
isLoading: isGetMetricsStatsLoading,
isError: isGetMetricsStatsError,
error: metricsStatsError,
reset: resetMetricsStats,
} = useGetMetricsStats();
const {
@@ -173,7 +169,6 @@ function Summary(): JSX.Element {
isLoading: isGetMetricsTreemapLoading,
isError: isGetMetricsTreemapError,
error: metricsTreemapError,
reset: resetMetricsTreemap,
} = useGetMetricsTreemap();
const metricsStatsApiError = useMemo(
@@ -198,40 +193,6 @@ function Summary(): JSX.Element {
});
}, [metricsTreemapQuery, getMetricsTreemap]);
const handleCancelQuery = useCallback(() => {
resetMetricsStats();
resetMetricsTreemap();
setCurrentQueryFilterExpression(appliedFilterExpression);
setIsCancelled(true);
}, [
resetMetricsStats,
resetMetricsTreemap,
setCurrentQueryFilterExpression,
appliedFilterExpression,
]);
const handleRunQuery = useCallback(() => {
setIsCancelled(false);
getMetricsStats({
data: {
...metricsListQuery,
filter: { expression: currentQueryFilterExpression },
},
});
getMetricsTreemap({
data: {
...metricsTreemapQuery,
filter: { expression: currentQueryFilterExpression },
},
});
}, [
getMetricsStats,
getMetricsTreemap,
metricsListQuery,
metricsTreemapQuery,
currentQueryFilterExpression,
]);
const handleFilterChange = useCallback(
(expression: string) => {
const newFilters: TagFilter = {
@@ -358,19 +319,11 @@ function Summary(): JSX.Element {
!isGetMetricsTreemapLoading &&
!isGetMetricsTreemapError;
const isLoadingQueries =
isGetMetricsStatsLoading || isGetMetricsTreemapLoading;
const showFullScreenLoading =
isLoadingQueries &&
(isGetMetricsStatsLoading || isGetMetricsTreemapLoading) &&
formattedMetricsData.length === 0 &&
!treeMapData?.data[heatmapView]?.length;
const showNoMetrics =
isMetricsListDataEmpty &&
isMetricsTreeMapDataEmpty &&
!appliedFilterExpression;
return (
<Sentry.ErrorBoundary fallback={<ErrorBoundaryFallback />}>
<div className="metrics-explorer-summary-tab">
@@ -379,30 +332,13 @@ function Summary(): JSX.Element {
onChange={handleFilterChange}
currentQueryFilterExpression={currentQueryFilterExpression}
setCurrentQueryFilterExpression={setCurrentQueryFilterExpression}
isLoading={isLoadingQueries}
handleCancelQuery={handleCancelQuery}
onRunQuery={handleRunQuery}
isLoading={isGetMetricsStatsLoading || isGetMetricsTreemapLoading}
/>
{showFullScreenLoading ? (
<MetricsLoading />
) : isCancelled ? (
<div className="no-logs-container">
<div className="no-logs-container-content">
<img
className="eyes-emoji"
src="/Images/eyesEmoji.svg"
alt="eyes emoji"
/>
<Typography className="no-logs-text">
Query cancelled.
<span className="sub-text">
{' '}
Click &quot;Run Query&quot; to load metrics.
</span>
</Typography>
</div>
</div>
) : showNoMetrics ? (
) : isMetricsListDataEmpty &&
isMetricsTreeMapDataEmpty &&
!appliedFilterExpression ? (
<NoLogs dataSource={DataSource.METRICS} />
) : (
<>

View File

@@ -29,8 +29,6 @@ export interface MetricsSearchProps {
currentQueryFilterExpression: string;
setCurrentQueryFilterExpression: (expression: string) => void;
isLoading: boolean;
handleCancelQuery: () => void;
onRunQuery: () => void;
}
export interface MetricsTreemapProps {

View File

@@ -4,6 +4,7 @@ import (
"context"
"encoding/json"
"fmt"
"maps"
"slices"
"strings"
@@ -12,6 +13,7 @@ import (
"github.com/SigNoz/signoz/pkg/errors"
"github.com/SigNoz/signoz/pkg/query-service/agentConf"
"github.com/SigNoz/signoz/pkg/query-service/constants"
"github.com/SigNoz/signoz/pkg/query-service/interfaces"
"github.com/SigNoz/signoz/pkg/query-service/model"
v3 "github.com/SigNoz/signoz/pkg/query-service/model/v3"
"github.com/SigNoz/signoz/pkg/query-service/utils"
@@ -34,19 +36,101 @@ type LogParsingPipelineController struct {
Repo
GetIntegrationPipelines func(context.Context, string) ([]pipelinetypes.GettablePipeline, error)
// TODO(Piyush): remove with qbv5 migration
reader interfaces.Reader
}
func NewLogParsingPipelinesController(
sqlStore sqlstore.SQLStore,
getIntegrationPipelines func(context.Context, string) ([]pipelinetypes.GettablePipeline, error),
reader interfaces.Reader,
) (*LogParsingPipelineController, error) {
repo := NewRepo(sqlStore)
return &LogParsingPipelineController{
Repo: repo,
GetIntegrationPipelines: getIntegrationPipelines,
reader: reader,
}, nil
}
// enrichPipelinesFilters resolves the type (tag vs resource) for filter keys that are
// missing type info, by looking them up in the store.
//
// TODO(Piyush): remove with qbv5 migration
func (pc *LogParsingPipelineController) enrichPipelinesFilters(
ctx context.Context, pipelines []pipelinetypes.GettablePipeline,
) ([]pipelinetypes.GettablePipeline, error) {
// Collect names of non-static keys that are missing type info.
// Static fields (body, trace_id, etc.) are intentionally Unspecified and map
// to top-level OTEL fields — they do not need enrichment.
unspecifiedNames := map[string]struct{}{}
for _, p := range pipelines {
if p.Filter != nil {
for _, item := range p.Filter.Items {
if item.Key.Type == v3.AttributeKeyTypeUnspecified {
// Skip static fields
if _, isStatic := constants.StaticFieldsLogsV3[item.Key.Key]; isStatic {
continue
}
// Skip enrich body.* fields
if strings.HasPrefix(item.Key.Key, "body.") {
continue
}
unspecifiedNames[item.Key.Key] = struct{}{}
}
}
}
}
if len(unspecifiedNames) == 0 {
return pipelines, nil
}
logFields, apiErr := pc.reader.GetLogFieldsFromNames(ctx, slices.Collect(maps.Keys(unspecifiedNames)))
if apiErr != nil {
slog.ErrorContext(ctx, "failed to fetch log fields for pipeline filter enrichment", "error", apiErr)
return pipelines, apiErr
}
// Build a simple name → AttributeKeyType map from the response.
fieldTypes := map[string]v3.AttributeKeyType{}
for _, f := range append(logFields.Selected, logFields.Interesting...) {
switch f.Type {
case constants.Resources:
fieldTypes[f.Name] = v3.AttributeKeyTypeResource
case constants.Attributes:
fieldTypes[f.Name] = v3.AttributeKeyTypeTag
}
}
// Set the resolved type on each untyped filter key in-place.
for i := range pipelines {
if pipelines[i].Filter != nil {
for j := range pipelines[i].Filter.Items {
key := &pipelines[i].Filter.Items[j].Key
if key.Type == v3.AttributeKeyTypeUnspecified {
// Skip static fields
if _, isStatic := constants.StaticFieldsLogsV3[key.Key]; isStatic {
continue
}
// Skip enrich body.* fields
if strings.HasPrefix(key.Key, "body.") {
continue
}
if t, ok := fieldTypes[key.Key]; ok {
key.Type = t
} else {
// default to attribute
key.Type = v3.AttributeKeyTypeTag
}
}
}
}
}
return pipelines, nil
}
// PipelinesResponse is used to prepare http response for pipelines config related requests
type PipelinesResponse struct {
*opamptypes.AgentConfigVersion
@@ -256,7 +340,12 @@ func (ic *LogParsingPipelineController) PreviewLogsPipelines(
ctx context.Context,
request *PipelinesPreviewRequest,
) (*PipelinesPreviewResponse, error) {
result, collectorLogs, err := SimulatePipelinesProcessing(ctx, request.Pipelines, request.Logs)
pipelines, err := ic.enrichPipelinesFilters(ctx, request.Pipelines)
if err != nil {
return nil, err
}
result, collectorLogs, err := SimulatePipelinesProcessing(ctx, pipelines, request.Logs)
if err != nil {
return nil, err
}
@@ -293,10 +382,8 @@ func (pc *LogParsingPipelineController) RecommendAgentConfig(
if configVersion != nil {
pipelinesVersion = configVersion.Version
}
pipelinesResp, err := pc.GetPipelinesByVersion(
context.Background(), orgId, pipelinesVersion,
)
ctx := context.Background()
pipelinesResp, err := pc.GetPipelinesByVersion(ctx, orgId, pipelinesVersion)
if err != nil {
return nil, "", err
}
@@ -306,12 +393,17 @@ func (pc *LogParsingPipelineController) RecommendAgentConfig(
return nil, "", errors.WrapInternalf(err, CodeRawPipelinesMarshalFailed, "could not serialize pipelines to JSON")
}
if querybuilder.BodyJSONQueryEnabled {
// add default normalize pipeline at the beginning, only for sending to collector
pipelinesResp.Pipelines = append([]pipelinetypes.GettablePipeline{pc.getNormalizePipeline()}, pipelinesResp.Pipelines...)
enrichedPipelines, err := pc.enrichPipelinesFilters(ctx, pipelinesResp.Pipelines)
if err != nil {
return nil, "", err
}
updatedConf, err := GenerateCollectorConfigWithPipelines(currentConfYaml, pipelinesResp.Pipelines)
if querybuilder.BodyJSONQueryEnabled {
// add default normalize pipeline at the beginning, only for sending to collector
enrichedPipelines = append([]pipelinetypes.GettablePipeline{pc.getNormalizePipeline()}, enrichedPipelines...)
}
updatedConf, err := GenerateCollectorConfigWithPipelines(currentConfYaml, enrichedPipelines)
if err != nil {
return nil, "", err
}

View File

@@ -121,6 +121,7 @@ func NewServer(config signoz.Config, signoz *signoz.SigNoz) (*Server, error) {
logParsingPipelineController, err := logparsingpipeline.NewLogParsingPipelinesController(
signoz.SQLStore,
integrationsController.GetPipelinesForInstalledIntegrations,
reader,
)
if err != nil {
return nil, err