logs: modify debug settings to let user choose between no logs, application logs only, application logs + liblinphone warns or all logs

This commit is contained in:
Gautier Pelloux-Prayer 2016-01-20 14:03:21 +01:00
parent 2644f9b9ac
commit a01c497570
13 changed files with 91 additions and 75 deletions

View file

@ -74,6 +74,23 @@
<action selector="onGotoLinphoneLoginClick:" destination="-1" eventType="touchUpInside" id="4AG-ng-fIB"/>
</connections>
</button>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" adjustsImageWhenHighlighted="NO" lineBreakMode="wordWrap" id="39" userLabel="loginButton" customClass="UIRoundBorderedButton">
<rect key="frame" x="40" y="301" width="299" height="40"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
<accessibility key="accessibilityConfiguration" label="Use SIP account">
<bool key="isElement" value="YES"/>
</accessibility>
<fontDescription key="fontDescription" type="system" pointSize="16"/>
<state key="normal" title="USE SIP ACCOUNT">
<color key="titleColor" red="0.2666666667" green="0.2666666667" blue="0.2666666667" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</state>
<state key="highlighted">
<color key="titleColor" red="0.72549019609999998" green="0.76862745099999996" blue="0.79607843140000001" alpha="1" colorSpace="deviceRGB"/>
</state>
<connections>
<action selector="onGotoLoginClick:" destination="-1" eventType="touchUpInside" id="qRw-RY-0ip"/>
</connections>
</button>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" adjustsImageWhenHighlighted="NO" lineBreakMode="wordWrap" id="Kbn-dL-C5h" userLabel="remoteProvisioningButton" customClass="UIRoundBorderedButton">
<rect key="frame" x="40" y="376" width="299" height="40"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
@ -91,23 +108,6 @@
<action selector="onGotoRemoteProvisioningClick:" destination="-1" eventType="touchUpInside" id="cAo-3u-yUT"/>
</connections>
</button>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" adjustsImageWhenHighlighted="NO" lineBreakMode="wordWrap" id="39" userLabel="loginButton" customClass="UIRoundBorderedButton">
<rect key="frame" x="39" y="301" width="299" height="40"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
<accessibility key="accessibilityConfiguration" label="Use SIP account">
<bool key="isElement" value="YES"/>
</accessibility>
<fontDescription key="fontDescription" type="system" pointSize="16"/>
<state key="normal" title="USE SIP ACCOUNT">
<color key="titleColor" red="0.2666666667" green="0.2666666667" blue="0.2666666667" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</state>
<state key="highlighted">
<color key="titleColor" red="0.72549019609999998" green="0.76862745099999996" blue="0.79607843140000001" alpha="1" colorSpace="deviceRGB"/>
</state>
<connections>
<action selector="onGotoLoginClick:" destination="-1" eventType="touchUpInside" id="qRw-RY-0ip"/>
</connections>
</button>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
<nil key="simulatedStatusBarMetrics"/>

View file

@ -195,11 +195,11 @@
<subviews>
<imageView userInteractionEnabled="NO" tag="17" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="waiting_time.png" id="DH6-pH-W9j" userLabel="pausedImage">
<rect key="frame" x="0.0" y="135" width="375" height="71"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
</imageView>
<label opaque="NO" userInteractionEnabled="NO" tag="18" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="You are paused by remote" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumScaleFactor="0.5" id="OgZ-xf-k7s" userLabel="pausedLabel">
<rect key="frame" x="0.0" y="175" width="375" height="100"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<nil key="highlightedColor"/>
@ -227,11 +227,11 @@
<subviews>
<imageView userInteractionEnabled="NO" tag="21" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="waiting_time.png" id="4uU-gc-9XK" userLabel="pausedImage">
<rect key="frame" x="0.0" y="200" width="375" height="71"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
</imageView>
<label opaque="NO" userInteractionEnabled="NO" tag="22" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="No active call" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumScaleFactor="0.5" id="b5q-sb-qbU" userLabel="pausedLabel">
<rect key="frame" x="0.0" y="241" width="375" height="100"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<nil key="highlightedColor"/>
@ -789,11 +789,11 @@
<subviews>
<imageView userInteractionEnabled="NO" tag="17" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="waiting_time.png" id="oQ7-Ye-Vf5" userLabel="pausedImage">
<rect key="frame" x="276" y="15" width="114" height="75"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
</imageView>
<label opaque="NO" userInteractionEnabled="NO" tag="18" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="You are paused by remote" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumScaleFactor="0.5" id="AK5-tl-4a6" userLabel="pausedLabel">
<rect key="frame" x="146" y="91" width="375" height="47"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<nil key="highlightedColor"/>
@ -821,11 +821,11 @@
<subviews>
<imageView userInteractionEnabled="NO" tag="21" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="waiting_time.png" id="8Pf-Le-JDT" userLabel="pausedImage">
<rect key="frame" x="276" y="80" width="114" height="75"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
</imageView>
<label opaque="NO" userInteractionEnabled="NO" tag="22" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="No active call" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumScaleFactor="0.5" id="Abl-Ep-xm0" userLabel="pausedLabel">
<rect key="frame" x="146" y="156" width="375" height="47"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<nil key="highlightedColor"/>

View file

@ -307,15 +307,17 @@ static UICompositeViewDescription *compositeDescription = nil;
[self presentMailViewWithTitle:appName forRecipients:@[ logsAddress ] attachLogs:true];
}];
BOOL debugEnabled = [LinphoneManager.instance lpConfigBoolForKey:@"debugenable_preference"];
int debugLevel = [LinphoneManager.instance lpConfigIntForKey:@"debugenable_preference"];
BOOL debugEnabled = (debugLevel >= ORTP_DEBUG && debugLevel < ORTP_ERROR);
NSString *actionLog =
(debugEnabled ? NSLocalizedString(@"Disable logs", nil) : NSLocalizedString(@"Enable logs", nil));
[alertView addButtonWithTitle:actionLog
block:^{
BOOL enabled = !debugEnabled;
[LinphoneManager.instance lpConfigSetBool:enabled forKey:@"debugenable_preference"];
[Log enableLogs:enabled];
}];
[alertView
addButtonWithTitle:actionLog
block:^{
int newDebugLevel = debugEnabled ? ORTP_ERROR : ORTP_DEBUG;
[LinphoneManager.instance lpConfigSetInt:newDebugLevel forKey:@"debugenable_preference"];
[Log enableLogs:newDebugLevel];
}];
[alertView show];
return true;

View file

@ -361,7 +361,7 @@
// advanced section
{
[self setBool:[lm lpConfigBoolForKey:@"debugenable_preference"] forKey:@"debugenable_preference"];
[self setObject:[lm lpConfigStringForKey:@"debugenable_preference"] forKey:@"debugenable_preference"];
[self setBool:ANIMATED forKey:@"animations_preference"];
[self setBool:[lm lpConfigBoolForKey:@"backgroundmode_preference"] forKey:@"backgroundmode_preference"];
[self setBool:[lm lpConfigBoolForKey:@"start_at_boot_preference"] forKey:@"start_at_boot_preference"];

View file

@ -280,9 +280,9 @@ struct codec_name_pref_table codec_pref_table[] = {{"speex", 8000, "speex_8k_pre
// set default values for first boot
if ([self lpConfigStringForKey:@"debugenable_preference"] == nil) {
#ifdef DEBUG
[self lpConfigSetBool:TRUE forKey:@"debugenable_preference"];
[self lpConfigSetInt:1 forKey:@"debugenable_preference"];
#else
[self lpConfigSetBool:FALSE forKey:@"debugenable_preference"];
[self lpConfigSetInt:0 forKey:@"debugenable_preference"];
#endif
}
@ -1439,7 +1439,7 @@ static BOOL libStarted = FALSE;
LOGI(@"linphonecore is already created");
return;
}
[Log enableLogs:[self lpConfigBoolForKey:@"debugenable_preference"]];
[Log enableLogs:[self lpConfigIntForKey:@"debugenable_preference"]];
connectivity = none;
ms_init(); // Need to initialize mediastreamer2 before loading the plugins
@ -1471,7 +1471,6 @@ static BOOL libStarted = FALSE;
.lastPathComponent]
?: [LinphoneManager bundleFile:@"hold.caf"])
.lastPathComponent;
linphone_core_set_ring(theLinphoneCore, [LinphoneManager bundleFile:ring].UTF8String);
linphone_core_set_ringback(theLinphoneCore, [LinphoneManager bundleFile:ringback].UTF8String);
linphone_core_set_play_file(theLinphoneCore, [LinphoneManager bundleFile:hold].UTF8String);

View file

@ -30,7 +30,7 @@
}
+ (void)log:(OrtpLogLevel)severity file:(const char *)file line:(int)line format:(NSString *)format, ...;
+ (void)enableLogs:(BOOL)enabled;
+ (void)enableLogs:(OrtpLogLevel)level;
void linphone_iphone_log_handler(const char *domain, OrtpLogLevel lev, const char *fmt, va_list args);
@end

View file

@ -410,12 +410,13 @@ static UICompositeViewDescription *compositeDescription = nil;
removeFromHiddenKeys = (stun_server && ([stun_server length] > 0));
[keys addObject:@"ice_preference"];
} else if ([@"debugenable_preference" compare:notif.object] == NSOrderedSame) {
BOOL debugEnabled = [[notif.userInfo objectForKey:@"debugenable_preference"] boolValue];
int debugLevel = [[notif.userInfo objectForKey:@"debugenable_preference"] intValue];
BOOL debugEnabled = (debugLevel >= ORTP_DEBUG && debugLevel < ORTP_ERROR);
removeFromHiddenKeys = debugEnabled;
[keys addObject:@"send_logs_button"];
[keys addObject:@"reset_logs_button"];
[Log enableLogs:debugEnabled];
[LinphoneManager.instance lpConfigSetBool:debugEnabled forKey:@"debugenable_preference"];
[Log enableLogs:debugLevel];
[LinphoneManager.instance lpConfigSetInt:debugLevel forKey:@"debugenable_preference"];
} else if ([@"account_mandatory_advanced_preference" compare:notif.object] == NSOrderedSame) {
removeFromHiddenKeys = [[notif.userInfo objectForKey:@"account_mandatory_advanced_preference"] boolValue];
for (NSString *key in settingsStore->dict) {

View file

@ -21,7 +21,7 @@
@implementation Log
#define FILESIZE 17
#define FILE_SIZE 17
#define DOMAIN_SIZE 3
+ (NSString *)cacheDirectory {
@ -45,29 +45,24 @@
NSString *str = [[NSString alloc] initWithFormat:format arguments:args];
const char *utf8str = [str cStringUsingEncoding:NSString.defaultCStringEncoding];
const char *filename = strchr(file, '/') ? strrchr(file, '/') + 1 : file;
if ((severity & ORTP_DEBUG) != 0) {
// lol: ortp_debug(XXX) can be disabled at compile time, but ortp_log(ORTP_DEBUG, xxx) will always be valid even
// not in debug build...
ortp_debug("%*s:%-4d/%s", FILESIZE, filename + MAX((int)strlen(filename) - FILESIZE, 0), line, utf8str);
} else {
ortp_log(severity, "%*s:%-4d/%s", FILESIZE, filename + MAX((int)strlen(filename) - FILESIZE, 0), line, utf8str);
}
ortp_log(severity, "(%*s:%-4d) %s", FILE_SIZE, filename + MAX((int)strlen(filename) - FILE_SIZE, 0), line, utf8str);
va_end(args);
}
+ (void)enableLogs:(BOOL)enabled {
+ (void)enableLogs:(OrtpLogLevel)level {
BOOL enabled = (level >= ORTP_DEBUG && level < ORTP_ERROR);
linphone_core_set_log_collection_path([self cacheDirectory].UTF8String);
linphone_core_enable_log_collection(enabled);
linphone_core_enable_logs_with_cb(linphone_iphone_log_handler);
if (enabled) {
NSLog(@"Enabling debug logs");
linphone_core_set_log_level(ORTP_DEBUG);
if (level == 0) {
NSLog(@"I/%s/Disabling all logs", ORTP_LOG_DOMAIN);
linphone_core_set_log_level(ORTP_FATAL);
ortp_set_log_level("ios", ORTP_FATAL);
} else {
NSLog(@"Disabling debug logs");
linphone_core_set_log_level(ORTP_ERROR);
NSLog(@"I/%s/Enabling %s logs", ORTP_LOG_DOMAIN, (enabled ? "all" : "application only"));
linphone_core_set_log_level(level);
ortp_set_log_level("ios", ORTP_DEBUG);
}
ortp_set_log_level_mask("ios", ORTP_DEBUG | ORTP_MESSAGE | ORTP_WARNING | ORTP_ERROR | ORTP_FATAL);
}
#pragma mark - Logs Functions callbacks
@ -76,16 +71,25 @@ void linphone_iphone_log_handler(const char *domain, OrtpLogLevel lev, const cha
NSString *format = [[NSString alloc] initWithUTF8String:fmt];
NSString *formatedString = [[NSString alloc] initWithFormat:format arguments:args];
NSString *lvl = @"";
if ((lev & ORTP_FATAL) != 0) {
lvl = @"F";
} else if ((lev & ORTP_ERROR) != 0) {
lvl = @"E";
} else if ((lev & ORTP_WARNING) != 0) {
lvl = @"W";
} else if ((lev & ORTP_MESSAGE) != 0) {
lvl = @"I";
} else if (((lev & ORTP_TRACE) != 0) || ((lev & ORTP_DEBUG) != 0)) {
lvl = @"D";
switch (lev) {
case ORTP_FATAL:
lvl = @"F";
break;
case ORTP_ERROR:
lvl = @"E";
break;
case ORTP_WARNING:
lvl = @"W";
break;
case ORTP_MESSAGE:
lvl = @"I";
break;
case ORTP_DEBUG:
case ORTP_TRACE:
lvl = @"D";
break;
case ORTP_LOGLEV_END:
return;
}
if (!domain)
domain = "liblinphone";

View file

@ -40,7 +40,7 @@ NSString *const kLogsUpdateNotification = @"kLogsUpdateNotification";
- (void)setupLogging {
lastLogs = [[NSMutableArray alloc] initWithCapacity:kLastLogsCapacity];
logsBuffer = [NSMutableArray arrayWithCapacity:kLogsBufferCapacity];
[Log enableLogs:YES];
[Log enableLogs:ORTP_DEBUG];
}
void tester_logs_handler(int level, const char *fmt, va_list args) {

Binary file not shown.

View file

@ -12,13 +12,27 @@
</dict>
<dict>
<key>DefaultValue</key>
<false/>
<string>0</string>
<key>Key</key>
<string>debugenable_preference</string>
<key>Title</key>
<string>Debug</string>
<key>Titles</key>
<array>
<string>None</string>
<string>Application only</string>
<string>Application and libraries warnings</string>
<string>All</string>
</array>
<key>Type</key>
<string>PSToggleSwitchSpecifier</string>
<string>PSMultiValueSpecifier</string>
<key>Values</key>
<array>
<string>0</string>
<string>32</string>
<string>8</string>
<string>1</string>
</array>
</dict>
<dict>
<key>Key</key>

View file

@ -487,7 +487,6 @@
639E9CB01C0DB83000019A75 /* SideMenuView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 639E9CB21C0DB83000019A75 /* SideMenuView.xib */; };
639E9CB51C0DB88200019A75 /* PhoneMainView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 639E9CB31C0DB88200019A75 /* PhoneMainView.xib */; };
63AADBE81B6A0FF200AA16FD /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 63AADBC41B6A0FF200AA16FD /* Localizable.strings */; };
63AADBE91B6A0FF200AA16FD /* hold.wav in Resources */ = {isa = PBXBuildFile; fileRef = 63AADBC91B6A0FF200AA16FD /* hold.wav */; };
63AADBEA1B6A0FF200AA16FD /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 63AADBCA1B6A0FF200AA16FD /* Images.xcassets */; };
63AADBF31B6A0FF200AA16FD /* licenses.html in Resources */ = {isa = PBXBuildFile; fileRef = 63AADBD51B6A0FF200AA16FD /* licenses.html */; };
63AADBF51B6A0FF200AA16FD /* linphonerc in Resources */ = {isa = PBXBuildFile; fileRef = 63AADBD71B6A0FF200AA16FD /* linphonerc */; };
@ -1319,7 +1318,6 @@
63AADBC61B6A0FF200AA16FD /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/Localizable.strings; sourceTree = "<group>"; };
63AADBC71B6A0FF200AA16FD /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = "<group>"; };
63AADBC81B6A0FF200AA16FD /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/Localizable.strings; sourceTree = "<group>"; };
63AADBC91B6A0FF200AA16FD /* hold.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; path = hold.wav; sourceTree = "<group>"; };
63AADBCA1B6A0FF200AA16FD /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = "<group>"; };
63AADBCB1B6A0FF200AA16FD /* ja */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ja; path = ja.lproj/Localizable.strings; sourceTree = "<group>"; };
63AADBD51B6A0FF200AA16FD /* licenses.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = licenses.html; sourceTree = "<group>"; };
@ -2508,7 +2506,6 @@
63AADBE41B6A0FF200AA16FD /* assistant_linphone_create.rc */,
63AADBE51B6A0FF200AA16FD /* assistant_linphone_existing.rc */,
63AADBE61B6A0FF200AA16FD /* assistant_remote.rc */,
63AADBC91B6A0FF200AA16FD /* hold.wav */,
63AADBCA1B6A0FF200AA16FD /* Images.xcassets */,
63AADBD51B6A0FF200AA16FD /* licenses.html */,
63AADBD71B6A0FF200AA16FD /* linphonerc */,
@ -3181,7 +3178,6 @@
636B991A1C298401003BA37C /* route_bluetooth_default@2x.png in Resources */,
636B99371C298401003BA37C /* select_all_default.png in Resources */,
636B99461C298401003BA37C /* valid_disabled@2x.png in Resources */,
63AADBE91B6A0FF200AA16FD /* hold.wav in Resources */,
63B81A0C1B57DA33009604A6 /* LICENSE.txt in Resources */,
636B986A1C298401003BA37C /* contact_add_default.png in Resources */,
636B98F61C298401003BA37C /* numpad_over_background.png in Resources */,

@ -1 +1 @@
Subproject commit 8508f5435738598b646b37fc5ecb951ecd000281
Subproject commit 33be9d61934d8864e65079d4bab957187ccb69c4