001package org.jpos.metrics.iso;
002
003import io.micrometer.core.instrument.MeterRegistry;
004import io.micrometer.core.instrument.Tags;
005import org.jpos.iso.ISOMsg;
006import org.jpos.metrics.MeterInfo;
007
008import java.util.ArrayList;
009import java.util.List;
010import java.util.stream.Collectors;
011
012public interface ISOMsgMetrics {
013    String DEFAULT_CHANNEL_METRIC_NAME = MeterInfo.ISOMSG_IN.id();
014
015    String ENV_CHANNEL_TAGS =   "metrics.channel.tags";
016    String DEFAULT_TAGS =       "name, type, direction";
017
018    String ENV_CHANNEL_FIELDS =     "metrics.channel.fields";
019    String DEFAULT_CHANNEL_FIELDS = "mti";
020
021    interface Source{
022        void setISOMsgMetrics(ISOMsgMetrics metrics);
023        ISOMsgMetrics getISOMsgMetrics();
024    }
025
026    void setMetricName(String metricName);
027    String getMetricName();
028
029    String getMetricDescription();
030    void setMetricDescription(String metricDescription);
031
032    Tags addTags(Tags tags);
033    default Tags addTags(String ...tags) { return addTags(Tags.of(tags)); }
034    default Tags getTags() { return addTags(Tags.empty()); }
035
036    /**
037     * Records an {@link ISOMsg} in the meter registry.<br/>
038     * The metric name and tags will be taken strictly from this object's
039     * configuration. <br/>
040     * If this object hasn't been successfully registered, it throws an
041     * {@link IllegalStateException}.
042     *
043     * @param m the {@link ISOMsg} to record.
044     * @throws IllegalStateException when this object hasn't been registered
045     */
046    void recordMessage(ISOMsg m) throws IllegalStateException;
047
048    /**
049     * Records an {@link ISOMsg} in the meter registry.<br/>
050     * Similar to {@link #recordMessage(ISOMsg)} but using the metric name, description and maybe some
051     * tags taken from the {@link MeterInfo} argument.
052     * <p>
053     * If the metric for that combination of {@link MeterInfo} values and local values fails to register
054     * in the global {@link MeterRegistry} (or any underlying one like the Prometheus registry), the method
055     * may throw an {@link IllegalStateException}.  This also happens if this object hasn't been successfully
056     * registered by The metric name and tags will be taken from
057     * what has been configured. If this object hasn't been successfully registered, it throws an
058     * {@link IllegalStateException}.
059     *
060     * @param m the {@link ISOMsg} to record.
061     * @throws IllegalStateException when this object hasn't been registered
062     */
063    void recordMessage(ISOMsg m, MeterInfo meterInfo) throws IllegalStateException;
064
065
066    /**
067     * Register this object to work with a given {@link MeterRegistry}.</br>
068     *
069     * This method may serve more than one purpose in the object's lifecycle:
070     * <ul>
071     *  <li>Assign a {@link MeterRegistry} to be used for the created meters.
072     *  (The registry can be obtained by calling {@link #getRegistry()})</li>
073     *  <li>Before this object has been registered, it can be configured by setting tags, etc.,
074     *      but attempting to record a message (e.g. through  {@link #recordMessage(ISOMsg)})
075     *      will throw an {@link IllegalStateException}.</li>
076     *  <li>After it has been registered, it's ready to record messages.
077     *      However, it <b>can't be configured</b> any longer, or it will throw an {@link IllegalStateException}.
078     *      The object's configuration can be considered "frozen".</li>
079     *  <li>In some (future) implementation, it may make use of the {@link #getMetricSignature()} to
080     *      do some caching to ensure that every metric name has only one set of tag keys, thus avoiding
081     *      metrics name+keyset collision in {@code PrometheusMeterRegistry}.</li>
082     * </ul>
083     *
084     *  The {@link #unregister()} method should be called when done using this object.
085     *
086     * @return true if successful, false if there was an error having this registered
087     */
088    boolean register(MeterRegistry registry);
089
090    /**
091     * It calls {@link #removeMeters()} and clears its internal reference to its {@link MeterRegistry}.<br/>
092     *
093     * It will also "unfreeze" the object, making it available for reconfiguration.
094     */
095    void unregister();
096
097    MeterRegistry getRegistry();
098
099    void removeMeters();
100
101
102    /**
103     * A unique meter signature, concatenating the meter name, vertical pipe, comma-separated sorted list of all tag keys
104     * that this object produces.<br/>
105     * Default implementation uses the values from {@link #getMetricName()} and {@link #getTags()}.
106     * A concrete implementation must make sure of gathering all the appropriate tag keys from internal config state
107     * which may include more than what's returned by {@link #getTags()}.
108     * @return  The unique metric signature.
109     */
110    default String getMetricSignature() {
111        List<String> keys = new ArrayList<>();
112        getTags().forEach(t -> keys.add(t.getKey()));
113        return getMetricName()+"|"+
114                keys.stream().sorted().collect(Collectors.joining(","));
115    }
116}