Current section

Files

Jump to
otters src otters_filter.erl
Raw

src/otters_filter.erl

%%%-------------------------------------------------------------------
%%% Licensed to the Apache Software Foundation (ASF) under one
%%% or more contributor license agreements. See the NOTICE file
%%% distributed with this work for additional information
%%% regarding copyright ownership. The ASF licenses this file
%%% to you under the Apache License, Version 2.0 (the
%%% "License"); you may not use this file except in compliance
%%% with the License. You may obtain a copy of the License at
%%%
%%% http://www.apache.org/licenses/LICENSE-2.0
%%%
%%% Unless required by applicable law or agreed to in writing,
%%% software distributed under the License is distributed on an
%%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
%%% KIND, either express or implied. See the License for the
%%% specific language governing permissions and limitations
%%% under the License.
%%%
%%%
%%% @doc
%%% The main idea behind this filter that the processing of the spans can
%%% be modified runtime by changing the filter configuration. This way
%%% logging, counting or sending data to trace collectors can be modified
%%% on the running system based on the changing operational requirements.
%%%
%%% The span filter works with key value pair lists. This was the
%%% easiest to implement and reasonably fast.
%%%
%%% Filter rules are composed by a list of `{Conditions, Actions}' tuples.
%%% Processing a span means iterating through this list and when an item
%%% found where all `Conditions' evaluate to true then the `Actions' in that
%%% item are executed.
%%%
%%% `Conditions' operate on a copy of the tags of the span. It is a
%%% sequence of checks against the tags (e.g. key present, key value)
%%% where if any check in the sequence fails, the associated actions are
%%% not executed and the next `{Conditions, Actions}' item is evaluated.
%%% `Actions' can trigger e.g. counting a particular tag value combination,
%%% logging, sending the span to trace collectors, modifying the working
%%% (i.e. used for further conditions) tag list, modifying the span tag
%%% list that is to be sent to trace collector. `Actions' can also
%%% influence the further evaluation of the rules i.e. providing the
%%% break action instructs the rule engine NOT to look further in the
%%% list of `{Conditions, Actions}'.
%%%
%%% `Evaluation' of the rules happens in the process which invokes the span
%%% end statement (e.g. @link otter:finish/1.) i.e. it has impact on the
%%% request processing time. Therefore the actions that consume little
%%% time and resources with no external interfaces (e.g. counting in ets)
%%% can be done during the evaluation of the rules, but anything that has
%%% external interface or dependent on environment (e.g. logging and trace
%%% collecting) should be done asynchronously.
%%% @end
%%%-------------------------------------------------------------------
-module(otters_filter).
-include("otters.hrl").
-export([span/1]).
%% The main idea behind this filter that the processing of the spans can
%% be modified runtime by changing the filter configuration. This way
%% logging, counting or sending data to trace collectors can be modified
%% on the running system based on the changing operational requirements.
%% The span filter works with key value pair lists easiest to implement
%% and reasonably fast.
%% Filter rules are composed by a list of {Conditions, Actions} tuples.
%% Processing a span means iterating through this list and when an item
%% found where all Conditions evaluate to true then the Actions in that
%% item are executed.
%% Conditions operate on a copy of the tags of the span. It is a
%% sequence of checks against the tags (e.g. key present, key value)
%% where if any check in the sequence fails, the associated actions are
%% not executed and the next {Conditions, Actions} item is evaluated.
%% Actions can trigger e.g. counting a particular tag value combination,
%% logging, sending the span to trace collectors, modifying the working
%% (i.e. used for further conditions) tag list, modifying the span tag
%% list that is to be sent to trace collector. Actions can also
%% influence the further evaluation of the rules i.e. providing the
%% break action instructs the rule engine NOT to look further in the
%% list of {Conditions, Actions}.
%% Evaluation of the rules happens in the process which invokes the span
%% end statement (e.g. otters_span:pend/0) i.e. it has impact on the
%% request processing time. Therefore the actions that consume little
%% time and resources with no external interfaces (e.g. counting in ets)
%% can be done during the evaluation of the rules, but anything that has
%% external interface or dependent on environment (e.g. logging and trace
%% collecting) should be done asynchronously.
-type filter() :: {[term()], [term()]}.
%%--------------------------------------------------------------------
%% @doc Takes a span as input, evaluates rules specified in
%% configuration and executes any specified actions.
%% @end
%% --------------------------------------------------------------------
-spec span(otters:span()) -> ok.
span(#span{tags = Tags, name = Name, duration = Duration} = Span) ->
case otters_config:read(filter_rules, undefined) of
undefined ->
ol:span(Span);
Rules ->
Tags1 = Tags#{
<<"otters_span_name">> => {Name, undefined},
<<"otters_span_duration">> => {Duration, undefined}
},
rules(Rules, Tags1, Span)
end.
-spec rules([filter()], otter:tags(), otter:span()) ->
ok.
rules([{Conditions, Actions} | Rest], Tags, Span) ->
case check_conditions(Conditions, Tags) of
false ->
rules(Rest, Tags, Span);
true ->
case do_actions(Actions, Tags, Span) of
break ->
ok;
{continue, NewTags, NewSpan} ->
rules(Rest, NewTags, NewSpan)
end
end;
rules(_, _, _) ->
ok.
check_conditions([Condition | Rest], Tags) ->
case check(Condition, Tags) of
true ->
check_conditions(Rest, Tags);
false ->
false
end;
check_conditions([], _) ->
true.
get_tag(Key, Tags) ->
KeyBin = otters_lib:to_bin(Key),
maps:find(KeyBin, Tags).
check({negate, Condition}, Tags) ->
not check(Condition, Tags);
check({value, Key, Value}, Tags) ->
case get_tag(Key, Tags) of
{ok, {Value, _}} ->
true;
_ ->
false
end;
check({same, Key1, Key2}, Tags) ->
case {get_tag(Key1, Tags),
get_tag(Key2, Tags)} of
{{ok, {Value, _}},
{ok, {Value, _}}} ->
true;
_ ->
false
end;
check({greater, Key, Value}, Tags) ->
case get_tag(Key, Tags) of
{ok, {Value1, _}} when Value1 > Value ->
true;
_ ->
false
end;
check({less, Key, Value}, Tags) ->
case get_tag(Key, Tags) of
{ok, {Value1, _}} when Value1 < Value ->
true;
_ ->
false
end;
check({between, Key, Value1, Value2}, Tags) ->
case get_tag(Key, Tags) of
{ok, {Value, _}} when Value > Value1 andalso Value < Value2 ->
true;
_ ->
false
end;
check({present, Key}, Tags) ->
case get_tag(Key, Tags) of
{ok, _} ->
true;
_ ->
false
end;
check(_, _) ->
false.
do_actions(Actions, Tags, Span) ->
do_actions(Actions, Tags, Span, continue).
do_actions([break | Rest], Tags, Span, _BreakOrContinue) ->
do_actions(Rest, Tags, Span, break);
do_actions([continue | Rest], Tags, Span, _BreakOrContinue) ->
do_actions(Rest, Tags, Span, continue);
do_actions([Action | Rest], Tags, Span, BreakOrContinue) ->
do_actions(Rest, action(Action, Tags, Span), Span, BreakOrContinue);
do_actions([], _Tags, _Span, break) ->
break;
do_actions([], Tags, Span, continue) ->
{continue, Tags, Span}.
action(send_to_zipkin, Tags, Span) ->
otters_conn_zipkin:store_span(Span),
Tags;
action({snapshot_count, Prefix, TagNames}, Tags, Span) ->
TagValues = [
case get_tag(Key, Tags) of
{ok, {Value, _}} -> Value;
_ -> undefined
end || Key <- TagNames],
otters_snapshot_count:snapshot(Prefix ++ TagValues, Span),
Tags;
action(_, Tags, _) ->
Tags.