using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Text; namespace StatsdClient { /// /// The statsd client library. /// public class Statsd : IStatsd { private string _prefix; private IOutputChannel _outputChannel; /// /// Creates a new instance of the Statsd client. /// /// The statsd or statsd.net server. /// public Statsd(string host, int port) { if ( String.IsNullOrEmpty( host ) ) { Trace.TraceWarning( "Statsd client initialised with empty host address. Dropping back to NullOutputChannel." ); InitialiseInternal( () => new NullOutputChannel(), "", false ); } else { InitialiseInternal( () => new UdpOutputChannel( host, port ), "", false ); } } /// /// Creates a new instance of the Statsd client. /// /// The statsd or statsd.net server. /// /// A string prefix to prepend to every metric. /// If True, rethrows any exceptions caught due to bad configuration. /// Choose between a UDP (recommended) or TCP connection. /// Retry the connection if it fails (TCP only). /// Number of times to retry before giving up (TCP only). public Statsd(string host, int port, ConnectionType connectionType = ConnectionType.Udp, string prefix = null, bool rethrowOnError = false, bool retryOnDisconnect = true, int retryAttempts = 3) { InitialiseInternal(() => { return connectionType == ConnectionType.Tcp ? (IOutputChannel)new TcpOutputChannel(host, port, retryOnDisconnect, retryAttempts) : (IOutputChannel)new UdpOutputChannel(host, port); }, prefix, rethrowOnError); } /// /// Creates a new instance of the Statsd client. /// /// The statsd or statsd.net server. /// /// A string prefix to prepend to every metric. /// If True, rethrows any exceptions caught due to bad configuration. /// Optional output channel (useful for mocking / testing). public Statsd(string host, int port, string prefix = null, bool rethrowOnError = false, IOutputChannel outputChannel = null) { if (outputChannel == null) { InitialiseInternal(() => new UdpOutputChannel(host, port), prefix, rethrowOnError); } else { InitialiseInternal(() => outputChannel, prefix, rethrowOnError); } } private void InitialiseInternal(Func createOutputChannel, string prefix, bool rethrowOnError) { _prefix = prefix; if (_prefix != null && _prefix.EndsWith(".")) { _prefix = _prefix.Substring(0, _prefix.Length - 1); } try { _outputChannel = createOutputChannel(); } catch (Exception ex) { if (rethrowOnError) { throw; } Trace.TraceError("Could not initialise the Statsd client: {0} - falling back to NullOutputChannel.", ex.Message); _outputChannel = new NullOutputChannel(); } } /// /// Log a counter. /// /// The metric name. /// The counter value (defaults to 1). public void LogCount(string name, int count = 1) { SendMetric(MetricType.COUNT, name, _prefix, count); } /// /// Log a timing / latency /// /// The metric name. /// The duration, in milliseconds, for this metric. public void LogTiming(string name, int milliseconds) { SendMetric(MetricType.TIMING, name, _prefix, milliseconds); } /// /// Log a timing / latency /// /// The metric name. /// The duration, in milliseconds, for this metric. public void LogTiming(string name, long milliseconds) { LogTiming(name, (int)milliseconds); } /// /// Log a gauge. /// /// The metric name /// The value for this gauge public void LogGauge(string name, int value) { SendMetric(MetricType.GAUGE, name, _prefix, value); } /// /// Log to a set /// /// The metric name. /// The value to log. /// Logging to a set is about counting the number /// of occurrences of each event. public void LogSet(string name, int value) { SendMetric(MetricType.SET, name, _prefix, value); } /// /// Log a calendargram metric /// /// The metric namespace /// The unique value to be counted in the time period /// The time period, can be one of h,d,dow,w,m public void LogCalendargram(string name, string value, string period) { SendMetric(MetricType.CALENDARGRAM, name, _prefix, value, period); } /// /// Log a calendargram metric /// /// The metric namespace /// The unique value to be counted in the time period /// The time period, can be one of h,d,dow,w,m public void LogCalendargram(string name, int value, string period) { SendMetric(MetricType.CALENDARGRAM, name, _prefix, value, period); } /// /// Log a raw metric that will not get aggregated on the server. /// /// The metric name. /// The metric value. /// (optional) The epoch timestamp. Leave this blank to have the server assign an epoch for you. public void LogRaw(string name, int value, long? epoch = null) { SendMetric(MetricType.RAW, name, String.Empty, value, epoch.HasValue ? epoch.ToString() : (string)null); } private void SendMetric(string metricType, string name, string prefix, int value, string postFix = null) { if (value < 0) { Trace.TraceWarning(String.Format("Metric value for {0} was less than zero: {1}. Not sending.", name, value)); return; } SendMetric(metricType, name, prefix, value.ToString(), postFix); } private void SendMetric(string metricType, string name, string prefix, string value, string postFix = null) { if (String.IsNullOrEmpty(name)) { throw new ArgumentNullException("name"); } _outputChannel.Send(PrepareMetric(metricType, name, prefix, value, postFix)); } /// /// Prepare a metric prior to sending it off ot the Graphite server. /// /// /// /// /// /// A value to append to the end of the line. /// The formatted metric protected virtual string PrepareMetric(string metricType, string name, string prefix, string value, string postFix = null) { return (String.IsNullOrEmpty(prefix) ? name : (prefix + "." + name)) + ":" + value + "|" + metricType + (postFix == null ? String.Empty : "|" + postFix); } } }