Ok - the json/bson serializer is in nuget (but I need to push an update to fix some known errors - don’t grab it just yet).
Here’s the data logger that I use (it is structured to work in the Verdant Service framework, but that is only evident in the Start/Stop methods):
using System;
using System.Collections;
using System.IO;
using System.Text;
using PervasiveDigital.Json;
using ProjectController.Common;
using ProjectController.Core.Utilities;
using Verdant.Node.Common;
namespace ProjectController.Core.Services
{
public class DataLoggerService : IService, IDataLogger
{
private const int MaxDays = 30;
private const UInt32 FileMarker = 0xDEADC0ED;
private const UInt32 RecordMarker = 0xBAADBEEF;
private readonly object _sync = new object();
private IMassStorageDriver _storage;
private FileStream _file;
private DateTime _lastDataWritten = DateTime.MinValue;
private DateTime _currentFileDate = DateTime.MinValue;
private UInt32 _offset;
public void Start()
{
_storage = (IMassStorageDriver)DiContainer.Instance.Resolve(typeof(IMassStorageDriver));
_storage.Insert += _storage_Insert;
_storage.Eject += _storage_Eject;
DiContainer.Instance.Register(typeof(IDataLogger), () => this);
if (_storage.GetSdCard(false) != null)
InitializeLogStorage();
}
public string Name { get { return "Data logger"; } }
public void Stop()
{
_file.Close();
_offset = 0;
_file = null;
}
public void LogEvent(string eventClass, object eventData)
{
lock (_sync)
{
var now = DateTime.UtcNow;
if (now.Year != _lastDataWritten.Year ||
now.Month != _lastDataWritten.Month ||
now.Day != _lastDataWritten.Day)
{
// roll over to new log file
InitializeLogStorage();
}
if (_file == null)
return;
var eventTree = JsonConverter.Serialize(eventData);
var wrapper = new JObject();
wrapper.Add("timestamp", new JValue(DateTimeExtensions.ToIso8601(now)));
wrapper.Add("classname", new JValue(eventData.GetType().FullName));
wrapper.Add("evclass", new JValue(eventClass));
wrapper.Add("evid", new JValue(Guid.NewGuid().ToString()));
wrapper.Add("data", eventTree);
var bson = wrapper.ToBson();
var length = bson.Length + 8;
var buffer = new byte[length];
int offset = 0;
SerializationUtilities.Marshall(buffer, ref offset, RecordMarker);
SerializationUtilities.Marshall(buffer, ref offset, (UInt32)length);
Array.Copy(bson, 0, buffer, offset, bson.Length);
// write record
_file.Seek(_offset, SeekOrigin.Begin);
_file.Write(buffer, 0, length);
// update file header with new offset
offset = 0;
SerializationUtilities.Marshall(buffer, ref offset, (UInt32)(_offset + length));
_file.Seek(4, SeekOrigin.Begin);
_file.Write(buffer, 0, 4);
_file.Flush();
_offset += (UInt32)length;
_storage.GetSdCard().FlushAll();
}
}
public IJToken[] GetEvents(DateTime start, int count)
{
lock (_sync)
{
ArrayList result = new ArrayList();
FileStream file = null;
UInt32 bookmark = 0;
do
{
// Get a file to work with
do
{
var compare = new DateTime(start.Year, start.Month, start.Day);
if (compare > _currentFileDate)
return (IJToken[])result.ToArray(typeof(IJToken));
if (file != null && file != _file)
file.Close();
file = GetFileFor(start);
if (file == null)
{
start = new DateTime(start.Year, start.Month, start.Day);
start = start.AddDays(1.0);
}
} while (file == null);
var record = GetFirstRecord(file, ref bookmark);
do
{
if (record is JObject)
{
try
{
var jobj = (JObject)record;
var value = (JValue)jobj["timestamp"].Value;
var timestamp = DateTimeExtensions.FromIso8601(value.Value.ToString());
if (timestamp > start)
{
result.Add(record);
--count;
}
}
catch
{
// timestamp might have been bad
//TODO: ??
}
}
// collect records until count==0 or end of file
record = GetNextRecord(file, ref bookmark);
if (record == null)
{
// move on to the next day
start = new DateTime(start.Year, start.Month, start.Day);
start = start.AddDays(1.0);
}
} while (count > 0 && record != null);
} while (count > 0);
if (file != _file)
file.Close();
return (IJToken[])result.ToArray(typeof(IJToken));
}
}
public IJToken[] GetEvents(DateTime fileDate, ref UInt32 bookmark, int count)
{
if (bookmark == 0)
bookmark = 8;
// normalize to midnight
fileDate = new DateTime(fileDate.Year, fileDate.Month, fileDate.Day);
if (fileDate > _currentFileDate)
throw new Exception("no data");
lock (_sync)
{
ArrayList result = new ArrayList();
FileStream file = GetFileFor(fileDate);
if (file == null)
throw new Exception("no data");
var record = GetNextRecord(file, ref bookmark);
if (record != null)
{
do
{
result.Add(record);
--count;
// collect records until count==0 or end of file
record = GetNextRecord(file, ref bookmark);
if (record == null)
break;
} while (count > 0 && record != null);
}
if (file != _file)
file.Close();
return (IJToken[])result.ToArray(typeof(IJToken));
}
}
public IJToken[] GetFiles()
{
var list = new ArrayList();
var rootDir = _storage.GetSdCard().RootDirectory;
var files = Directory.GetFiles(rootDir);
foreach (var file in files)
{
var filename = Path.GetFileName(file);
if (filename.StartsWith("log-") && filename.EndsWith(".dat"))
{
list.Add(new JValue(filename));
}
}
return (IJToken[])list.ToArray(typeof(IJToken));
}
private FileStream GetFileFor(DateTime date)
{
if (date.Year == _currentFileDate.Year &&
date.Month == _currentFileDate.Month &&
date.Day == _currentFileDate.Day)
return _file;
FileStream result = null;
var rootDir = _storage.GetSdCard().RootDirectory;
// Opening files takes time - make sure it is there first
var files = Directory.GetFiles(rootDir);
foreach (var file in files)
{
var filename = Path.GetFileName(file);
if (filename.StartsWith("log-") && filename.EndsWith(".dat"))
{
var year = int.Parse(filename.Substring(4, 4));
var month = int.Parse(filename.Substring(8, 2));
var day = int.Parse(filename.Substring(10, 2));
var fileDate = new DateTime(year, month, day);
if (fileDate.Year == date.Year &&
fileDate.Month == date.Month &&
fileDate.Day == date.Day)
{
try
{
var name = GetLogFileName(date);
result = File.Open(Path.Combine(rootDir, name), FileMode.Open, FileAccess.Read, FileShare.None);
}
catch
{
result = null;
}
return result;
}
}
}
return null;
}
private JToken GetFirstRecord(FileStream file, ref UInt32 cursor)
{
if (file.Length < 16)
return null;
var buffer = new byte[8];
int offset = 0;
file.Seek(0, SeekOrigin.Begin);
file.Read(buffer, 0, 8);
var fileMarker = (UInt32)SerializationUtilities.Unmarshall(buffer, ref offset, TypeCode.UInt32);
if (fileMarker != FileMarker)
return null;
var nextRecord = (UInt32)SerializationUtilities.Unmarshall(buffer, ref offset, TypeCode.UInt32);
file.Seek(8, SeekOrigin.Begin);
file.Read(buffer, 0, 8);
offset = 0;
var recordMarker = (UInt32)SerializationUtilities.Unmarshall(buffer, ref offset, TypeCode.UInt32);
if (recordMarker != RecordMarker)
return null;
var length = (UInt32)SerializationUtilities.Unmarshall(buffer, ref offset, TypeCode.UInt32);
var dataBuffer = new byte[length - 8];
if (file.Read(dataBuffer, 0, (int)(length - 8)) != length - 8)
return null;
var result = JsonConverter.FromBson(dataBuffer);
((JObject)result).Add("locn", new JValue(cursor));
((JObject)result).Add("next", new JValue(cursor + length));
cursor = 8 + length;
return result;
}
private JToken GetNextRecord(FileStream file, ref UInt32 cursor)
{
if (file.Length <= cursor)
return null;
var buffer = new byte[8];
int offset = 0;
file.Seek(cursor, SeekOrigin.Begin);
if (file.Read(buffer, 0, 8)!=8)
return null;
var recordMarker = (UInt32)SerializationUtilities.Unmarshall(buffer, ref offset, TypeCode.UInt32);
if (recordMarker != RecordMarker)
return null;
var length = (UInt32)SerializationUtilities.Unmarshall(buffer, ref offset, TypeCode.UInt32);
var dataBuffer = new byte[length - 8];
if (file.Read(dataBuffer, 0, (int)(length - 8)) != length - 8)
return null;
var result = JsonConverter.FromBson(dataBuffer);
((JObject)result).Add("locn", new JValue(cursor));
((JObject)result).Add("next", new JValue(cursor+length));
cursor = cursor + length;
return result;
}
private void InitializeLogStorage()
{
lock (_sync)
{
try
{
if (_file != null)
{
_file.Close();
_file = null;
_offset = 0;
}
var rootDir = _storage.GetSdCard().RootDirectory;
var now = DateTime.UtcNow;
CleanUp(rootDir, now);
_file = File.Open(Path.Combine(rootDir, GetLogFileName(now)), FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None);
_currentFileDate = new DateTime(now.Year, now.Month, now.Day);
InitializeLogFile();
_lastDataWritten = now;
}
catch
{
_file = null;
_offset = 0;
}
}
}
private void CleanUp(string root, DateTime today)
{
try
{
// remove old files
var files = Directory.GetFiles(root);
foreach (var file in files)
{
var filename = Path.GetFileName(file);
if (filename.StartsWith("log-") && filename.EndsWith(".dat"))
{
var year = int.Parse(filename.Substring(4, 4));
var month = int.Parse(filename.Substring(8, 2));
var day = int.Parse(filename.Substring(10, 2));
var fileDate = new DateTime(year, month, day);
if (today - fileDate > TimeSpan.FromTicks(TimeSpan.TicksPerHour * 24 * MaxDays))
{
try
{
File.Delete(file);
}
catch
{
// don't let this stop cleanup attempts
}
}
}
}
}
catch
{
// failure to clean up is not fatal, though the effect could accumulate
}
}
private void InitializeLogFile()
{
var buffer = new byte[8];
int offset = 0;
if (_file.Length < 8)
{
InitializeEmptyFile(_file);
_offset = 8;
return;
}
_file.Seek(0, SeekOrigin.Begin);
_file.Read(buffer, 0, 8);
var marker = (UInt32)SerializationUtilities.Unmarshall(buffer, ref offset, TypeCode.UInt32);
var nextRecord = (UInt32)SerializationUtilities.Unmarshall(buffer, ref offset, TypeCode.UInt32);
if (marker != FileMarker || nextRecord < 8)
{
InitializeEmptyFile(_file);
_offset = 8;
return;
}
_offset = nextRecord;
}
private void InitializeEmptyFile(FileStream file)
{
var buffer = new byte[8];
int offset = 0;
SerializationUtilities.Marshall(buffer, ref offset, FileMarker);
SerializationUtilities.Marshall(buffer, ref offset, (UInt32)8);
file.Seek(0, SeekOrigin.Begin);
file.Write(buffer, 0, 8);
file.Flush();
}
private string GetLogFileName(DateTime dt)
{
string timestamp = dt.Year.ToString("D4") + dt.Month.ToString("D2") + dt.Day.ToString("D2");
return "log-" + timestamp + ".dat";
}
void _storage_Insert(object sender, Microsoft.SPOT.IO.MediaEventArgs e)
{
if (e.Volume.Name == "SD")
InitializeLogStorage();
}
void _storage_Eject(object sender, Microsoft.SPOT.IO.MediaEventArgs e)
{
if (e.Volume.Name == "SD")
{
_file = null;
_offset = 0;
}
}
}
public class DataLoggerEvent
{
public int recordType;
public string recordSubType;
public long timestamp;
}
}
And here is an example of walking a log to turn BSON into JSON for transmitting over HTTP: (in this case, a web request came in with either a date and a count, or a date and a file offset or ‘cursor’)
IJToken[] records;
if (uiCursor == UInt32.MaxValue)
records = _logger.GetEvents(dtStart, iCount);
else
records = _logger.GetEvents(dtStart, ref uiCursor, iCount);
var result = new JArray(records);
SendResponse(ctx, HttpStatusCode.OK, result.ToString());