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);
}
}
}