与分析服务集成的架构/设计模式(flurry/google 分析)
Posted
技术标签:
【中文标题】与分析服务集成的架构/设计模式(flurry/google 分析)【英文标题】:Architeture/Design pattern for integration with analytics services (flurry/google analytics) 【发布时间】:2014-05-05 23:55:49 【问题描述】:我正在开发一个 ios 应用程序,并计划在其中使用 分析 服务,例如 Flurry 或 Google Analytics。问题是:使用这些服务的良好软件设计(松散耦合、高度内聚、易于维护)是什么?
这是我的一个个人项目,我正在使用它来研究新技术和最佳实践。我偶然发现了这个“挑战”,并没有真正找到最佳解决方案。
我已经开发了一些使用这种服务的移动应用程序,并且通常我实现了适配器 + 工厂设计模式的组合:
创建代表通用分析服务的基本接口
public interface IAnalytics
void LogEvent(string name, object param);
每个服务(Flurry/Google Analytics/etc)API 都通过使用实现此接口的适配器进行封装
public class FlurryService : IAnalyticsService
public LogEvent(sring name, object param)
Flurry.Log(name, param.ToString());
实现了一个工厂,以便我们在该特定应用程序上拥有所需的任何分析服务
public static class AnalyticsServiceFactory
public IAnalytics CreateService(string servicename)
if(servicename == "google")
return new GoogleAnalyticsService();
else
return new FlurryService();
最后,创建了一个“代理”对象(不是按规定)来记录特定于应用程序的事件
public class MyAnalytics
private static IAnalyticsService _Service = AnalyticsServiceFactory.CreateService("flurry");
public static void LogUserLoggedIn(string user)
_Service.LogEvent("login", user);
public static void LogLoginFailed(string user)
_Service.LogEvent("login", user);
这处理了每个服务 API 的封装并且效果很好,特别是在不同平台之间共享代码的应用中。
然而,还有一个问题是事件(或用户执行的操作)本身的日志记录。在我处理过的所有情况下,无论何时发生此类事件,事件的记录都是硬编码的。例如:
public void LogIn(string userName, string pass)
bool success = this.Controller.LogIn(userName, pass);
if(success)
MyAnalytics.LogUserLoggedIn(username);
// Change view
else
MyAnalytics.LogLogInFailed(username);
// Alert
这似乎比我想要的更耦合,所以我正在寻找更好的解决方案。
在使用 iOS 时,我考虑过使用 NSNotificationCenter:每当事件发生时,我不会立即记录它,而是在 NSNotificationCenter 中发布通知,另一个观察这些通知的对象负责调用 MyAnalytics 记录事件。然而,这种设计仅适用于 iOS(也就是说,不需要大量的工作)。
看待这个问题的另一种方式是:游戏如何跟踪你的行为以达到 Xbox 成就/Playstation 奖杯?
【问题讨论】:
你决定选择什么? @edelaney05 最后,我决定采用我在问题中提出的建议:使用通知中心发布有关事件发生的通知,然后让这些通知的侦听器处理日志记录和所有事情。每个通知都带有事件 ID 和带有相关信息(例如触发事件的用户的名称)的对象(通常是字符串)。在 iOS 之外,您将不得不实现通知中心,这很糟糕,但如果您注意可能存在的内存泄漏,它可能会正常工作。 我发现了这种方法 - aspect-oriented programming - 但感觉有点太聪明了。 【参考方案1】:这是我通常用于可以是多提供者的日志记录类的设计。它允许在分析、崩溃报告和 beta OTA 提供商之间轻松交换,并且它使用预处理器指令,以便某些服务在某些环境中处于活动状态。此示例使用 CocoaLumberjack,但您可以使其与您选择的日志框架一起使用。
@class CLLocation;
@interface TGLogger : NSObject
+ (void)startLogging;
+ (void)stopLogging;
+ (NSString *)getUdidKey;
+ (void)setUserID:(NSString *)userID;
+ (void)setUsername:(NSString *)username;
+ (void)setUserEmail:(NSString *)email;
+ (void)setUserAge:(NSUInteger)age;
+ (void)setUserGender:(NSString *)gender;
+ (void)setUserLocation:(CLLocation *)location;
+ (void)setUserValue:(NSString *)value forKey:(NSString *)key;
+ (void)setIntValue:(int)value forKey:(NSString *)key;
+ (void)setFloatValue:(float)value forKey:(NSString *)key;
+ (void)setBoolValue:(BOOL)value forKey:(NSString *)key;
extern void TGReportMilestone(NSString *milestone, NSDictionary *parameters);
extern void TGReportBeginTimedMilestone(NSString *milestone, NSDictionary *parameters);
extern void TGReportEndTimedMilestone(NSString *milestone, NSDictionary *parameters);
@end
现在是实现文件:
#import "TGLogger.h"
#import "TGAppDelegate.h"
#import <CocoaLumberjack/DDASLLogger.h>
#import <CocoaLumberjack/DDTTYLogger.h>
#import <AFNetworkActivityLogger/AFNetworkActivityLogger.h>
@import CoreLocation;
#ifdef USE_CRASHLYTICS
#import <Crashlytics/Crashlytics.h>
#import <CrashlyticsLumberjack/CrashlyticsLogger.h>
#endif
#ifdef USE_FLURRY
#import <FlurrySDK/Flurry.h>
#endif
#import <Flurry.h>
@implementation TGLogger
+ (void)startLogging
[DDLog addLogger:[DDASLLogger sharedInstance]];
[DDLog addLogger:[DDTTYLogger sharedInstance]];
[[DDTTYLogger sharedInstance] setColorsEnabled:YES];
[[DDTTYLogger sharedInstance] setForegroundColor:[UIColor blueColor] backgroundColor:nil forFlag:LOG_FLAG_INFO];
[[DDTTYLogger sharedInstance] setForegroundColor:[UIColor orangeColor] backgroundColor:nil forFlag:LOG_FLAG_WARN];
[[DDTTYLogger sharedInstance] setForegroundColor:[UIColor redColor] backgroundColor:nil forFlag:LOG_FLAG_ERROR];
[[AFNetworkActivityLogger sharedLogger] startLogging];
#ifdef DEBUG
[[AFNetworkActivityLogger sharedLogger] setLevel:AFLoggerLevelInfo];
#else
[[AFNetworkActivityLogger sharedLogger] setLevel:AFLoggerLevelWarn];
#endif
#if defined(USE_CRASHLYTICS) || defined(USE_FLURRY)
NSString *udid = [TGLogger getUdidKey];
TGLogInfo(@"Current UDID is: %@", udid);
#endif
#ifdef USE_CRASHLYTICS
// Start Crashlytics
[Crashlytics startWithAPIKey:TGCrashlyticsKey];
[Crashlytics setUserIdentifier:udid];
[DDLog addLogger:[CrashlyticsLogger sharedInstance]];
TGLogInfo(@"Crashlytics started with API Key: %@", TGCrashlyticsKey);
#endif
#ifdef USE_FLURRY
[Flurry setAppVersion:[[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleShortVersionString"]];
[Flurry setSecureTransportEnabled:YES];
[Flurry setShowErrorInLogEnabled:YES];
[Flurry setLogLevel:FlurryLogLevelCriticalOnly];
[Flurry startSession:TGFlurryApiKey];
TGLogInfo(@"Flurry started with API Key %@ and for version %@", TGFlurryApiKey, [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleShortVersionString"]);
TGLogInfo(@"Flurry Agent Version %@", [Flurry getFlurryAgentVersion]);
#endif
TGLogInfo(@"Logging services started");
+ (void)stopLogging
TGLogInfo(@"Shutting down logging services");
[DDLog removeAllLoggers];
+ (NSString *)getUdidKey
return [[UIDevice currentDevice] identifierForVendor].UUIDString;
+ (void)setUserID:(NSString *)userID
#ifdef USE_CRASHLYTICS
[Crashlytics setUserIdentifier:userID];
#endif
+ (void)setUsername:(NSString *)username
#ifdef USE_CRASHLYTICS
[Crashlytics setUserName:username];
#endif
#ifdef USE_FLURRY
[Flurry setUserID:username];
#endif
+ (void)setUserEmail:(NSString *)email
#ifdef USE_CRASHLYTICS
[Crashlytics setUserEmail:email];
#endif
+ (void)setUserAge:(NSUInteger)age
#ifdef USE_FLURRY
[Flurry setAge:(int)age];
#endif
+ (void)setUserGender:(NSString *)gender
#ifdef USE_FLURRY
[Flurry setGender:gender];
#endif
+ (void)setUserLocation:(CLLocation *)location
#ifdef USE_FLURRY
[Flurry setLatitude:location.coordinate.latitude longitude:location.coordinate.longitude horizontalAccuracy:location.horizontalAccuracy verticalAccuracy:location.verticalAccuracy];
#endif
#ifdef USE_CRASHLYTICS
[Crashlytics setObjectValue:location forKey:@"location"];
#endif
+ (void)setUserValue:(NSString *)value forKey:(NSString *)key
#ifdef USE_CRASHLYTICS
[Crashlytics setObjectValue:value forKey:key];
#endif
#pragma mark - Report key/values with crash logs
+ (void)setIntValue:(int)value forKey:(NSString *)key
#ifdef USE_CRASHLYTICS
[Crashlytics setIntValue:value forKey:key];
#endif
+ (void)setBoolValue:(BOOL)value forKey:(NSString *)key
#ifdef USE_CRASHLYTICS
[Crashlytics setBoolValue:value forKey:key];
#endif
+ (void)setFloatValue:(float)value forKey:(NSString *)key
#ifdef USE_CRASHLYTICS
[Crashlytics setFloatValue:value forKey:key];
#endif
void TGReportMilestone(NSString *milestone, NSDictionary *parameters)
NSCParameterAssert(milestone);
TGLogCInfo(@"Reporting %@", milestone);
#ifdef USE_FLURRY
[Flurry logEvent:milestone withParameters:parameters];
#endif
void TGReportBeginTimedMilestone(NSString *milestone, NSDictionary *parameters)
NSCParameterAssert(milestone);
TGLogCInfo(@"Starting timed event %@", milestone);
#ifdef USE_FLURRY
[Flurry logEvent:milestone withParameters:parameters timed:YES];
#endif
void TGReportEndTimedMilestone(NSString *milestone, NSDictionary *parameters)
NSCParameterAssert(milestone);
TGLogCInfo(@"Ending timed event %@", milestone);
#ifdef USE_FLURRY
[Flurry endTimedEvent:milestone withParameters:parameters];
#endif
@end
【讨论】:
这有帮助吗?您会注意到,因为它与您的日志记录相结合,您根本不参与分析,如果您不想使用分析,它实际上只是充当日志语句。因此,无需使用 NSNotificationCenter 进行外来连接,您只需拥有一个记录事件并可选择将其报告给您选择的服务的功能。我在许多生产应用程序中都有这种设计,并根据需要在几分钟内切换了提供程序,甚至将其与 NewRelic 或多个分析提供程序等相关联。 感谢您的回复。当必须记录事件时,您只需在代码中调用 TGReportMilestone() 函数,还是在事件和记录之间存在抽象层?这是我在问题的最后一个代码示例中引用的问题。编辑:对不起,我发表了不完整的评论,似乎没有你的回复。我会读的。对不起。 抽象是 TGReportMilestone 本身只是将事件记录到您的日志系统(在这种情况下,TGLogLogCInfo 只是我使用的一个宏,它在调试模式下映射到 NSLog)。 USE_FLURRY ifdef 是一个编译器指令,表示如果在预处理器指令中设置了 USE_FLURRY=1,它也会将事件报告给 Flurry。但如果不是,那么它根本不使用该代码,实际上它甚至不导入标头。这使您的分析集成完全隔离且易于更改,同时默认充当简单的记录器。 我明白你的意思,不过,老实说,我会找到一种方法来封装 Flurry API 和其他日志提供程序。建议的方式太容易修改而不是扩展,但我理解这可能只是一个例子。无论如何,我认为对于我的这个项目,我会使用各种想法,感谢您的贡献!以上是关于与分析服务集成的架构/设计模式(flurry/google 分析)的主要内容,如果未能解决你的问题,请参考以下文章