Created
June 21, 2010 19:21
-
-
Save millenomi/447340 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // | |
| // ILTelemetry.h | |
| // Telemetry | |
| // | |
| // Created by ∞ on 21/06/10. | |
| // Copyright 2010 __MyCompanyName__. All rights reserved. | |
| // | |
| #import <Foundation/Foundation.h> | |
| // Use instead of NSLog. | |
| // L0Log is for use inside method calls; L0CLog is for use inside C functions. | |
| #define L0Log(x, ...) [[ILTelemetry sharedInstance] log:[NSString stringWithFormat:@"" x, ## __VA_ARGS__] fromSource:self function:__func__ file:__FILE__ line:__LINE__] | |
| #define L0CLog(x, ...) [[ILTelemetry sharedInstance] log:[NSString stringWithFormat:@"" x, ## __VA_ARGS__] fromSource:nil function:__func__ file:__FILE__ line:__LINE__] | |
| // Events are what telemetry logs. You usually don't create them; instead, use L0Log/L0CLog to log strings. | |
| @interface ILTelemetryEvent : NSObject {} | |
| @property(copy) NSDate* date; | |
| @property(copy) id payload; // A property list fragment (not necessarily array or dict). | |
| @property(copy) NSString* sourceName; | |
| @property(copy) NSString* sourceIdentity; | |
| @property(copy) NSString* sourceKind; | |
| - (void) setSourceFromObject:(id) o; | |
| @property(copy) NSString* functionName; | |
| @property(copy) NSString* fileName; | |
| @property(assign) unsigned long long fileLine; | |
| @property(readonly) NSString* consoleDescription; | |
| + eventWithPayload:(id) payload /* a plist object, not necessarily a root */ source:(id) source function:(const char*) function file:(const char*) file line:(unsigned long long) line; | |
| @property(readonly) NSDictionary* structuredDescription; | |
| @end | |
| #define kILTelemetryNetworkService @"_iltelemetry._tcp." | |
| /* | |
| Service info: | |
| - The telemetry service will cache the first service it sees with the above type. | |
| It must point to a HTTP server. | |
| It may have a 'path' key in its TXT dictionary whose contents are a path to be appended to form the final URL (eg: /some/path). | |
| Telemetry will POST the following JSON payload (with content-type application/json): | |
| { | |
| "displayName": "iPhone", // The name of the device | |
| "droppedEvents": 0, // The number of events telemetry had to drop to contain memory use. | |
| "events": [ | |
| { | |
| // NONE of the following have strong enough guarantees to be parsed automatically! | |
| // The source of this event. Any of these can be null. | |
| // For ObjC, the source is self for methods and nil for C functions. | |
| "sourceName": "<XyzAppDelegate 0x12345>", // the -description of self | |
| "sourceKind": "XyzAppDelegate", // the class of self | |
| "sourceIdentity": "0x12345", // the pointer value of self | |
| // call site details | |
| "functionName": "-[XyzAppDelegate doStuff]", | |
| "fileName": "/Projects/Xyz/XyzAppDelegate.m", | |
| "fileLine": 123, | |
| // date for event as a string in RFC 2822 format | |
| "date": // etc | |
| // The event payload. Can be anything, but the current impl only sends strings (log messages). | |
| "payload": "Did some stuff with final value = 12345" | |
| }, | |
| // more events | |
| ] | |
| } | |
| */ | |
| @interface ILTelemetry : NSObject | |
| #if __MAC_OS_X_VERSION_MAX_ALLOWED >= 1060 || __IPHONE_OS_VERSION_MAX_ALLOWED >= 40000 | |
| <NSNetServiceBrowserDelegate, NSNetServiceDelegate> | |
| #endif | |
| { | |
| NSString* displayName; | |
| NSMutableArray* eventsQueue; | |
| NSUInteger droppedEventsCount; | |
| NSNetServiceBrowser* browser; | |
| BOOL canSendEvents; | |
| NSNetService* currentService; | |
| BOOL searching; | |
| NSURLConnection* currentConnection; | |
| } | |
| + (ILTelemetry*) sharedInstance; | |
| - (void) log:(NSString*) message fromSource:(id) source function:(const char*) function file:(const char*) file line:(unsigned long long) line; | |
| - (void) logEvent:(ILTelemetryEvent*) e; | |
| @property(readonly) NSArray* enqueuedEvents; | |
| @end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // | |
| // ILTelemetry.m | |
| // Telemetry | |
| // | |
| // Created by ∞ on 21/06/10. | |
| // Copyright 2010 __MyCompanyName__. All rights reserved. | |
| // | |
| #import "ILTelemetry.h" | |
| #import "JSON.h" | |
| #import "NSDateFormatter-RFC2822.h" | |
| #import <unistd.h> | |
| @implementation ILTelemetryEvent | |
| + eventWithPayload:(id) payload /* a plist object, not necessary a root */ source:(id) source function:(const char*) function file:(const char*) file line:(unsigned long long) line; | |
| { | |
| ILTelemetryEvent* me = [[ILTelemetryEvent new] autorelease]; | |
| [me setSourceFromObject:source]; | |
| me.payload = payload; | |
| me.functionName = [NSString stringWithCString:function encoding:NSUTF8StringEncoding]; | |
| me.fileName = [NSString stringWithCString:file encoding:NSUTF8StringEncoding]; | |
| me.fileLine = line; | |
| me.date = [NSDate date]; | |
| return me; | |
| } | |
| - (void) setSourceFromObject:(id)o; | |
| { | |
| self.sourceName = [o description]; | |
| self.sourceKind = [[o class] description]; | |
| self.sourceIdentity = o? [NSString stringWithFormat:@"%p", (void*) o] : nil; | |
| } | |
| - (NSString*) description; | |
| { | |
| return [NSString stringWithFormat:@"%@ (from %@ (%@) at %@ %@:%llu)\n%@\n", [super description], self.sourceName, self.sourceKind, self.functionName, self.fileName, self.fileLine, self.payload]; | |
| } | |
| - (NSString*) consoleDescription; | |
| { | |
| return [NSString stringWithFormat:@"%@ (%@):\n-\t%@\n\n", self.sourceIdentity, self.functionName, self.payload]; | |
| } | |
| - (void) dealloc | |
| { | |
| self.payload = nil; | |
| self.functionName = nil; | |
| self.sourceKind = nil; | |
| self.sourceName = nil; | |
| self.sourceIdentity = nil; | |
| self.fileName = nil; | |
| self.date = nil; | |
| [super dealloc]; | |
| } | |
| @synthesize payload, date; | |
| @synthesize sourceKind, sourceName, sourceIdentity; | |
| @synthesize functionName, fileName, fileLine; | |
| - (NSDictionary *) structuredDescription; | |
| { | |
| NSMutableDictionary* desc = [[[self dictionaryWithValuesForKeys:[NSArray arrayWithObjects:@"payload", @"sourceKind", @"sourceName", @"sourceIdentity", @"functionName", @"fileName", @"fileLine", nil]] mutableCopy] autorelease]; | |
| if (self.date) | |
| [desc setObject:[NSDateFormatter RFC2822StringFromDate:self.date] forKey:@"date"]; | |
| return desc; | |
| } | |
| @end | |
| @interface ILTelemetry () | |
| - (void) startSearch; | |
| - (NSURL *) URLToCurrentService; | |
| - (NSString *) displayName; | |
| - (void) sendEnqueuedMessages; | |
| @end | |
| @implementation ILTelemetry | |
| + (ILTelemetry*) sharedInstance { | |
| static id myself = nil; | |
| if (!myself) | |
| myself = [[self alloc] init]; | |
| return myself; | |
| } | |
| - (id) init | |
| { | |
| self = [super init]; | |
| if (self != nil) { | |
| eventsQueue = [NSMutableArray new]; | |
| [self performSelectorOnMainThread:@selector(startSearch) withObject:nil waitUntilDone:YES]; | |
| } | |
| return self; | |
| } | |
| - (void) dealloc | |
| { | |
| currentService.delegate = nil; | |
| [currentService release]; | |
| [currentConnection cancel]; | |
| [currentConnection release]; | |
| [displayName release]; | |
| browser.delegate = nil; | |
| [browser stop]; | |
| [browser release]; | |
| [eventsQueue release]; | |
| [super dealloc]; | |
| } | |
| - (NSString*) displayName; | |
| { | |
| if (!displayName) { | |
| NSString* name = nil; | |
| #if TARGET_OS_MAC && !TARGET_OS_IPHONE | |
| NSHost* h = [NSHost currentHost]; | |
| if ([h respondsToSelector:@selector(localizedName)]) | |
| name = [h localizedName]; | |
| else | |
| name = [h name]; | |
| #endif | |
| if (!name) { | |
| char hostnameC[255 /* HOST_NAME_MAX */ + 1]; | |
| if (gethostname(hostnameC, sizeof(hostnameC)) != -1) | |
| name = [[[NSString alloc] initWithCString:hostnameC encoding:NSASCIIStringEncoding] autorelease]; | |
| else | |
| name = @"(n/a)"; | |
| } | |
| displayName = [name copy]; | |
| } | |
| return displayName; | |
| } | |
| #pragma mark Event logging | |
| - (void) log:(NSString*) message fromSource:(id) source function:(const char*) function file:(const char*) file line:(unsigned long long) line; | |
| { | |
| ILTelemetryEvent* e = [ILTelemetryEvent eventWithPayload:message source:source function:function file:file line:line]; | |
| NSLog(@"%@", e.consoleDescription); | |
| [self logEvent:e]; | |
| } | |
| - (void) logEvent:(ILTelemetryEvent*) e; | |
| { | |
| @synchronized(self) { | |
| if ([eventsQueue count] >= 100 /* TODO configurable */) { | |
| [eventsQueue removeObjectAtIndex:0]; | |
| if (droppedEventsCount < NSUIntegerMax) | |
| droppedEventsCount++; | |
| } | |
| [eventsQueue addObject:e]; | |
| [self performSelectorOnMainThread:@selector(sendEnqueuedMessages) withObject:nil waitUntilDone:NO]; | |
| } | |
| } | |
| - (NSArray*) enqueuedEvents; | |
| { | |
| NSArray* d; | |
| @synchronized(self) { | |
| d = [[eventsQueue copy] autorelease]; | |
| } | |
| return d; | |
| } | |
| #pragma mark Bonjour discovery | |
| - (void) startSearch; | |
| { | |
| if (!browser) { | |
| browser = [[NSNetServiceBrowser alloc] init]; | |
| browser.delegate = self; | |
| } | |
| if (!searching) { | |
| searching = YES; | |
| [browser searchForServicesOfType:kILTelemetryNetworkService inDomain:@""]; | |
| } | |
| } | |
| - (void) netServiceBrowser:(NSNetServiceBrowser *)aNetServiceBrowser didNotSearch:(NSDictionary *)errorDict; | |
| { | |
| searching = NO; | |
| NSLog(@"An error occurred while starting a Bonjour search for a telemetry server. Retrying in 5 s. Error: %@", errorDict); | |
| [self performSelector:@selector(startSearch) withObject:nil afterDelay:5.0]; | |
| } | |
| - (void) netServiceBrowserDidStopSearch:(NSNetServiceBrowser *)aNetServiceBrowser; | |
| { | |
| [self startSearch]; | |
| } | |
| - (void) netServiceBrowser:(NSNetServiceBrowser *)aNetServiceBrowser didFindService:(NSNetService *)aNetService moreComing:(BOOL)moreComing; | |
| { | |
| if (!currentService) { | |
| currentService = [aNetService retain]; | |
| currentService.delegate = self; | |
| [currentService resolveWithTimeout:5.0]; | |
| } | |
| } | |
| - (void)netServiceBrowser:(NSNetServiceBrowser *)aNetServiceBrowser didRemoveService:(NSNetService *)aNetService moreComing:(BOOL)moreComing; | |
| { | |
| if ([[currentService name] isEqual:[aNetService name]]) { | |
| canSendEvents = NO; | |
| [currentService release]; currentService = nil; | |
| } | |
| } | |
| - (void)netServiceDidResolveAddress:(NSNetService *)sender; | |
| { | |
| if (!canSendEvents) { | |
| if (![self URLToCurrentService]) { | |
| [currentService release]; currentService = nil; | |
| return; | |
| } | |
| canSendEvents = YES; | |
| [self sendEnqueuedMessages]; | |
| } | |
| } | |
| - (void)netService:(NSNetService *)sender didNotResolve:(NSDictionary *)errorDict; | |
| { | |
| NSLog(@"An error occurred while starting a Bonjour search for a telemetry server. Retrying in 5 s. Error: %@", errorDict); | |
| canSendEvents = NO; | |
| [currentService release]; currentService = nil; | |
| } | |
| #pragma mark Sending | |
| - (void) sendEnqueuedMessages; | |
| { | |
| if (!canSendEvents || currentConnection) | |
| return; | |
| NSURL* u = [self URLToCurrentService]; | |
| if (!u) | |
| return; | |
| NSArray* messagesToSend; NSUInteger dropped; | |
| @synchronized(self) { | |
| if ([eventsQueue count] == 0) | |
| return; | |
| messagesToSend = [[eventsQueue copy] autorelease]; | |
| [eventsQueue removeAllObjects]; | |
| dropped = droppedEventsCount; | |
| droppedEventsCount = 0; | |
| } | |
| NSMutableArray* events = [NSMutableArray arrayWithCapacity:[messagesToSend count]]; | |
| for (ILTelemetryEvent* event in messagesToSend) | |
| [events addObject:event.structuredDescription]; | |
| NSData* json = [[[NSDictionary dictionaryWithObjectsAndKeys: | |
| events, @"events", | |
| [NSNumber numberWithUnsignedInteger:dropped], @"droppedEvents", | |
| [self displayName], @"displayName", | |
| nil] JSONRepresentation] dataUsingEncoding:NSASCIIStringEncoding]; | |
| if (!json) | |
| return; | |
| NSMutableURLRequest* req = [NSMutableURLRequest requestWithURL:u]; | |
| [req setHTTPMethod:@"POST"]; | |
| [req setHTTPBody:json]; | |
| [req setValue:@"application/json" forHTTPHeaderField:@"Content-Type"]; | |
| currentConnection = [[NSURLConnection alloc] initWithRequest:req delegate:self]; | |
| } | |
| - (void) connectionDidFinishLoading:(NSURLConnection *)connection; | |
| { | |
| [currentConnection autorelease]; currentConnection = nil; | |
| } | |
| - (void) connection:(NSURLConnection *)connection didFailWithError:(NSError *)error; | |
| { | |
| [currentConnection autorelease]; currentConnection = nil; | |
| } | |
| - (NSURL*) URLToCurrentService; | |
| { | |
| NSDictionary* d = [NSNetService dictionaryFromTXTRecordData:[currentService TXTRecordData]]; | |
| if (!d) | |
| return nil; | |
| id path = [d objectForKey:@"path"]; | |
| if ([path isKindOfClass:[NSData class]]) { | |
| path = [[[NSString alloc] initWithData:path encoding:NSASCIIStringEncoding] autorelease]; | |
| if (!path) | |
| return nil; | |
| } | |
| if (!path) | |
| path = @"/"; | |
| if (![path hasPrefix:@"/"]) | |
| path = [@"/" stringByAppendingString:path]; | |
| NSString* s = [NSString stringWithFormat:@"http://%@:%d%@", [currentService hostName], (int) [currentService port], path]; | |
| return [NSURL URLWithString:s]; | |
| } | |
| @end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // | |
| // NSDateFormatter-RFC2822.h | |
| // wikimeety | |
| // | |
| // Created by Emanuele Vulcano on 07/12/09. | |
| // Copyright 2009 Command Guru. All rights reserved. | |
| // | |
| /* | |
| Copyright (c) 2009, Command Guru | |
| All rights reserved. | |
| Redistribution and use in source and binary forms, with or without | |
| modification, are permitted provided that the following conditions are met: | |
| * Redistributions of source code must retain the above copyright | |
| notice, this list of conditions and the following disclaimer. | |
| * Redistributions in binary form must reproduce the above copyright | |
| notice, this list of conditions and the following disclaimer in the | |
| documentation and/or other materials provided with the distribution. | |
| * Neither the name of the Command Guru nor the | |
| names of its contributors may be used to endorse or promote products | |
| derived from this software without specific prior written permission. | |
| THIS SOFTWARE IS PROVIDED BY Command Guru ''AS IS'' AND ANY | |
| EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | |
| WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | |
| DISCLAIMED. IN NO EVENT SHALL Command Guru BE LIABLE FOR ANY | |
| DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | |
| (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | |
| LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND | |
| ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | |
| SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| */ | |
| #import <Foundation/Foundation.h> | |
| @interface NSDateFormatter (WMRFC2822) | |
| + (NSDateFormatter*) RFC2822DateFormatter; | |
| + (NSDate*) dateFromRFC2822String:(NSString*) s; | |
| + (NSString*) RFC2822StringFromDate:(NSDate*) s; | |
| @end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // | |
| // NSDateFormatter-RFC2822.m | |
| // wikimeety | |
| // | |
| // Created by Emanuele Vulcano on 07/12/09. | |
| // Copyright 2009 Command Guru. All rights reserved. | |
| // | |
| /* | |
| Copyright (c) 2009, Command Guru | |
| All rights reserved. | |
| Redistribution and use in source and binary forms, with or without | |
| modification, are permitted provided that the following conditions are met: | |
| * Redistributions of source code must retain the above copyright | |
| notice, this list of conditions and the following disclaimer. | |
| * Redistributions in binary form must reproduce the above copyright | |
| notice, this list of conditions and the following disclaimer in the | |
| documentation and/or other materials provided with the distribution. | |
| * Neither the name of the Command Guru nor the | |
| names of its contributors may be used to endorse or promote products | |
| derived from this software without specific prior written permission. | |
| THIS SOFTWARE IS PROVIDED BY Command Guru ''AS IS'' AND ANY | |
| EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | |
| WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | |
| DISCLAIMED. IN NO EVENT SHALL Command Guru BE LIABLE FOR ANY | |
| DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | |
| (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | |
| LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND | |
| ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | |
| SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| */ | |
| #import "NSDateFormatter-RFC2822.h" | |
| @implementation NSDateFormatter (WMRFC2822) | |
| + (NSDateFormatter*) RFC2822DateFormatter; | |
| { | |
| static NSDateFormatter* me = nil; | |
| if (!me) { | |
| me = [self new]; | |
| [me setFormatterBehavior:NSDateFormatterBehavior10_4]; | |
| [me setLocale:[[[NSLocale alloc] initWithLocaleIdentifier:@"en_US"] autorelease]]; | |
| [me setDateFormat:@"EEE, dd MMM yyyy HH:mm:ss ZZ"]; | |
| } | |
| return me; | |
| } | |
| + (NSDate*) dateFromRFC2822String:(NSString*) s; | |
| { | |
| return [[self RFC2822DateFormatter] dateFromString:s]; | |
| } | |
| + (NSString*) RFC2822StringFromDate:(NSDate*) d; | |
| { | |
| return [[self RFC2822DateFormatter] stringFromDate:d]; | |
| } | |
| @end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| require 'rack' | |
| # To run: | |
| # sudo gem install thin | |
| # thin start -R telemetry.rb | |
| Telemetry = Rack::Builder.app do | |
| app = proc do |env| | |
| $stderr << env.inspect + "\n\n" | |
| $stderr << env['rack.input'].read + "\n\n" | |
| [ | |
| 200, # Status code | |
| { # Response headers | |
| 'Content-Type' => 'text/html', | |
| 'Content-Length' => '2', | |
| }, | |
| ['hi'] # Response body | |
| ] | |
| end | |
| # You can install Rack middlewares | |
| # to do some crazy stuff like logging, | |
| # filtering, auth or build your own. | |
| use Rack::CommonLogger | |
| run app | |
| end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment