DDTTYLogger.m 46 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487
  1. // Software License Agreement (BSD License)
  2. //
  3. // Copyright (c) 2010-2019, Deusty, LLC
  4. // All rights reserved.
  5. //
  6. // Redistribution and use of this software in source and binary forms,
  7. // with or without modification, are permitted provided that the following conditions are met:
  8. //
  9. // * Redistributions of source code must retain the above copyright notice,
  10. // this list of conditions and the following disclaimer.
  11. //
  12. // * Neither the name of Deusty nor the names of its contributors may be used
  13. // to endorse or promote products derived from this software without specific
  14. // prior written permission of Deusty, LLC.
  15. #import "DDTTYLogger.h"
  16. #import <sys/uio.h>
  17. #if !__has_feature(objc_arc)
  18. #error This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC).
  19. #endif
  20. // We probably shouldn't be using DDLog() statements within the DDLog implementation.
  21. // But we still want to leave our log statements for any future debugging,
  22. // and to allow other developers to trace the implementation (which is a great learning tool).
  23. //
  24. // So we use primitive logging macros around NSLog.
  25. // We maintain the NS prefix on the macros to be explicit about the fact that we're using NSLog.
  26. #ifndef DD_NSLOG_LEVEL
  27. #define DD_NSLOG_LEVEL 2
  28. #endif
  29. #define NSLogError(frmt, ...) do{ if(DD_NSLOG_LEVEL >= 1) NSLog((frmt), ##__VA_ARGS__); } while(0)
  30. #define NSLogWarn(frmt, ...) do{ if(DD_NSLOG_LEVEL >= 2) NSLog((frmt), ##__VA_ARGS__); } while(0)
  31. #define NSLogInfo(frmt, ...) do{ if(DD_NSLOG_LEVEL >= 3) NSLog((frmt), ##__VA_ARGS__); } while(0)
  32. #define NSLogDebug(frmt, ...) do{ if(DD_NSLOG_LEVEL >= 4) NSLog((frmt), ##__VA_ARGS__); } while(0)
  33. #define NSLogVerbose(frmt, ...) do{ if(DD_NSLOG_LEVEL >= 5) NSLog((frmt), ##__VA_ARGS__); } while(0)
  34. // Xcode does NOT natively support colors in the Xcode debugging console.
  35. // You'll need to install the XcodeColors plugin to see colors in the Xcode console.
  36. // https://github.com/robbiehanson/XcodeColors
  37. //
  38. // The following is documentation from the XcodeColors project:
  39. //
  40. //
  41. // How to apply color formatting to your log statements:
  42. //
  43. // To set the foreground color:
  44. // Insert the ESCAPE_SEQ into your string, followed by "fg124,12,255;" where r=124, g=12, b=255.
  45. //
  46. // To set the background color:
  47. // Insert the ESCAPE_SEQ into your string, followed by "bg12,24,36;" where r=12, g=24, b=36.
  48. //
  49. // To reset the foreground color (to default value):
  50. // Insert the ESCAPE_SEQ into your string, followed by "fg;"
  51. //
  52. // To reset the background color (to default value):
  53. // Insert the ESCAPE_SEQ into your string, followed by "bg;"
  54. //
  55. // To reset the foreground and background color (to default values) in one operation:
  56. // Insert the ESCAPE_SEQ into your string, followed by ";"
  57. #define XCODE_COLORS_ESCAPE_SEQ "\033["
  58. #define XCODE_COLORS_RESET_FG XCODE_COLORS_ESCAPE_SEQ "fg;" // Clear any foreground color
  59. #define XCODE_COLORS_RESET_BG XCODE_COLORS_ESCAPE_SEQ "bg;" // Clear any background color
  60. #define XCODE_COLORS_RESET XCODE_COLORS_ESCAPE_SEQ ";" // Clear any foreground or background color
  61. // If running in a shell, not all RGB colors will be supported.
  62. // In this case we automatically map to the closest available color.
  63. // In order to provide this mapping, we have a hard-coded set of the standard RGB values available in the shell.
  64. // However, not every shell is the same, and Apple likes to think different even when it comes to shell colors.
  65. //
  66. // Map to standard Terminal.app colors (1), or
  67. // map to standard xterm colors (0).
  68. #define MAP_TO_TERMINAL_APP_COLORS 1
  69. typedef struct {
  70. uint8_t r;
  71. uint8_t g;
  72. uint8_t b;
  73. } DDRGBColor;
  74. @interface DDTTYLoggerColorProfile : NSObject {
  75. @public
  76. DDLogFlag mask;
  77. NSInteger context;
  78. uint8_t fg_r;
  79. uint8_t fg_g;
  80. uint8_t fg_b;
  81. uint8_t bg_r;
  82. uint8_t bg_g;
  83. uint8_t bg_b;
  84. NSUInteger fgCodeIndex;
  85. NSString *fgCodeRaw;
  86. NSUInteger bgCodeIndex;
  87. NSString *bgCodeRaw;
  88. char fgCode[24];
  89. size_t fgCodeLen;
  90. char bgCode[24];
  91. size_t bgCodeLen;
  92. char resetCode[8];
  93. size_t resetCodeLen;
  94. }
  95. - (instancetype)initWithForegroundColor:(DDColor *)fgColor backgroundColor:(DDColor *)bgColor flag:(DDLogFlag)mask context:(NSInteger)ctxt;
  96. @end
  97. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  98. #pragma mark -
  99. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  100. @interface DDTTYLogger () {
  101. NSString *_appName;
  102. char *_app;
  103. size_t _appLen;
  104. NSString *_processID;
  105. char *_pid;
  106. size_t _pidLen;
  107. BOOL _colorsEnabled;
  108. NSMutableArray *_colorProfilesArray;
  109. NSMutableDictionary *_colorProfilesDict;
  110. }
  111. @end
  112. @implementation DDTTYLogger
  113. static BOOL isaColorTTY;
  114. static BOOL isaColor256TTY;
  115. static BOOL isaXcodeColorTTY;
  116. static NSArray *codes_fg = nil;
  117. static NSArray *codes_bg = nil;
  118. static NSArray *colors = nil;
  119. static DDTTYLogger *sharedInstance;
  120. /**
  121. * Initializes the colors array, as well as the codes_fg and codes_bg arrays, for 16 color mode.
  122. *
  123. * This method is used when the application is running from within a shell that only supports 16 color mode.
  124. * This method is not invoked if the application is running within Xcode, or via normal UI app launch.
  125. **/
  126. + (void)initialize_colors_16 {
  127. if (codes_fg || codes_bg || colors) {
  128. return;
  129. }
  130. NSMutableArray *m_colors = [NSMutableArray arrayWithCapacity:16];
  131. // In a standard shell only 16 colors are supported.
  132. //
  133. // More information about ansi escape codes can be found online.
  134. // http://en.wikipedia.org/wiki/ANSI_escape_code
  135. codes_fg = @[
  136. @"30m", // normal - black
  137. @"31m", // normal - red
  138. @"32m", // normal - green
  139. @"33m", // normal - yellow
  140. @"34m", // normal - blue
  141. @"35m", // normal - magenta
  142. @"36m", // normal - cyan
  143. @"37m", // normal - gray
  144. @"1;30m", // bright - darkgray
  145. @"1;31m", // bright - red
  146. @"1;32m", // bright - green
  147. @"1;33m", // bright - yellow
  148. @"1;34m", // bright - blue
  149. @"1;35m", // bright - magenta
  150. @"1;36m", // bright - cyan
  151. @"1;37m", // bright - white
  152. ];
  153. codes_bg = @[
  154. @"40m", // normal - black
  155. @"41m", // normal - red
  156. @"42m", // normal - green
  157. @"43m", // normal - yellow
  158. @"44m", // normal - blue
  159. @"45m", // normal - magenta
  160. @"46m", // normal - cyan
  161. @"47m", // normal - gray
  162. @"1;40m", // bright - darkgray
  163. @"1;41m", // bright - red
  164. @"1;42m", // bright - green
  165. @"1;43m", // bright - yellow
  166. @"1;44m", // bright - blue
  167. @"1;45m", // bright - magenta
  168. @"1;46m", // bright - cyan
  169. @"1;47m", // bright - white
  170. ];
  171. #if MAP_TO_TERMINAL_APP_COLORS
  172. // Standard Terminal.app colors:
  173. //
  174. // These are the default colors used by Apple's Terminal.app.
  175. DDRGBColor rgbColors[] = {
  176. { 0, 0, 0}, // normal - black
  177. {194, 54, 33}, // normal - red
  178. { 37, 188, 36}, // normal - green
  179. {173, 173, 39}, // normal - yellow
  180. { 73, 46, 225}, // normal - blue
  181. {211, 56, 211}, // normal - magenta
  182. { 51, 187, 200}, // normal - cyan
  183. {203, 204, 205}, // normal - gray
  184. {129, 131, 131}, // bright - darkgray
  185. {252, 57, 31}, // bright - red
  186. { 49, 231, 34}, // bright - green
  187. {234, 236, 35}, // bright - yellow
  188. { 88, 51, 255}, // bright - blue
  189. {249, 53, 248}, // bright - magenta
  190. { 20, 240, 240}, // bright - cyan
  191. {233, 235, 235}, // bright - white
  192. };
  193. #else /* if MAP_TO_TERMINAL_APP_COLORS */
  194. // Standard xterm colors:
  195. //
  196. // These are the default colors used by most xterm shells.
  197. DDRGBColor rgbColors[] = {
  198. { 0, 0, 0}, // normal - black
  199. {205, 0, 0}, // normal - red
  200. { 0, 205, 0}, // normal - green
  201. {205, 205, 0}, // normal - yellow
  202. { 0, 0, 238}, // normal - blue
  203. {205, 0, 205}, // normal - magenta
  204. { 0, 205, 205}, // normal - cyan
  205. {229, 229, 229}, // normal - gray
  206. {127, 127, 127}, // bright - darkgray
  207. {255, 0, 0}, // bright - red
  208. { 0, 255, 0}, // bright - green
  209. {255, 255, 0}, // bright - yellow
  210. { 92, 92, 255}, // bright - blue
  211. {255, 0, 255}, // bright - magenta
  212. { 0, 255, 255}, // bright - cyan
  213. {255, 255, 255}, // bright - white
  214. };
  215. #endif /* if MAP_TO_TERMINAL_APP_COLORS */
  216. for (size_t i = 0; i < sizeof(rgbColors) / sizeof(rgbColors[0]); ++i) {
  217. [m_colors addObject:DDMakeColor(rgbColors[i].r, rgbColors[i].g, rgbColors[i].b)];
  218. }
  219. colors = [m_colors copy];
  220. NSAssert([codes_fg count] == [codes_bg count], @"Invalid colors/codes array(s)");
  221. NSAssert([codes_fg count] == [colors count], @"Invalid colors/codes array(s)");
  222. }
  223. /**
  224. * Initializes the colors array, as well as the codes_fg and codes_bg arrays, for 256 color mode.
  225. *
  226. * This method is used when the application is running from within a shell that supports 256 color mode.
  227. * This method is not invoked if the application is running within Xcode, or via normal UI app launch.
  228. **/
  229. + (void)initialize_colors_256 {
  230. if (codes_fg || codes_bg || colors) {
  231. return;
  232. }
  233. NSMutableArray *m_codes_fg = [NSMutableArray arrayWithCapacity:(256 - 16)];
  234. NSMutableArray *m_codes_bg = [NSMutableArray arrayWithCapacity:(256 - 16)];
  235. NSMutableArray *m_colors = [NSMutableArray arrayWithCapacity:(256 - 16)];
  236. #if MAP_TO_TERMINAL_APP_COLORS
  237. // Standard Terminal.app colors:
  238. //
  239. // These are the colors the Terminal.app uses in xterm-256color mode.
  240. // In this mode, the terminal supports 256 different colors, specified by 256 color codes.
  241. //
  242. // The first 16 color codes map to the original 16 color codes supported by the earlier xterm-color mode.
  243. // These are actually configurable, and thus we ignore them for the purposes of mapping,
  244. // as we can't rely on them being constant. They are largely duplicated anyway.
  245. //
  246. // The next 216 color codes are designed to run the spectrum, with several shades of every color.
  247. // While the color codes are standardized, the actual RGB values for each color code is not.
  248. // Apple's Terminal.app uses different RGB values from that of a standard xterm.
  249. // Apple's choices in colors are designed to be a little nicer on the eyes.
  250. //
  251. // The last 24 color codes represent a grayscale.
  252. //
  253. // Unfortunately, unlike the standard xterm color chart,
  254. // Apple's RGB values cannot be calculated using a simple formula (at least not that I know of).
  255. // Also, I don't know of any ways to programmatically query the shell for the RGB values.
  256. // So this big giant color chart had to be made by hand.
  257. //
  258. // More information about ansi escape codes can be found online.
  259. // http://en.wikipedia.org/wiki/ANSI_escape_code
  260. // Colors
  261. DDRGBColor rgbColors[] = {
  262. { 47, 49, 49},
  263. { 60, 42, 144},
  264. { 66, 44, 183},
  265. { 73, 46, 222},
  266. { 81, 50, 253},
  267. { 88, 51, 255},
  268. { 42, 128, 37},
  269. { 42, 127, 128},
  270. { 44, 126, 169},
  271. { 56, 125, 209},
  272. { 59, 124, 245},
  273. { 66, 123, 255},
  274. { 51, 163, 41},
  275. { 39, 162, 121},
  276. { 42, 161, 162},
  277. { 53, 160, 202},
  278. { 45, 159, 240},
  279. { 58, 158, 255},
  280. { 31, 196, 37},
  281. { 48, 196, 115},
  282. { 39, 195, 155},
  283. { 49, 195, 195},
  284. { 32, 194, 235},
  285. { 53, 193, 255},
  286. { 50, 229, 35},
  287. { 40, 229, 109},
  288. { 27, 229, 149},
  289. { 49, 228, 189},
  290. { 33, 228, 228},
  291. { 53, 227, 255},
  292. { 27, 254, 30},
  293. { 30, 254, 103},
  294. { 45, 254, 143},
  295. { 38, 253, 182},
  296. { 38, 253, 222},
  297. { 42, 253, 252},
  298. {140, 48, 40},
  299. {136, 51, 136},
  300. {135, 52, 177},
  301. {134, 52, 217},
  302. {135, 56, 248},
  303. {134, 53, 255},
  304. {125, 125, 38},
  305. {124, 125, 125},
  306. {122, 124, 166},
  307. {123, 124, 207},
  308. {123, 122, 247},
  309. {124, 121, 255},
  310. {119, 160, 35},
  311. {117, 160, 120},
  312. {117, 160, 160},
  313. {115, 159, 201},
  314. {116, 158, 240},
  315. {117, 157, 255},
  316. {113, 195, 39},
  317. {110, 194, 114},
  318. {111, 194, 154},
  319. {108, 194, 194},
  320. {109, 193, 234},
  321. {108, 192, 255},
  322. {105, 228, 30},
  323. {103, 228, 109},
  324. {105, 228, 148},
  325. {100, 227, 188},
  326. { 99, 227, 227},
  327. { 99, 226, 253},
  328. { 92, 253, 34},
  329. { 96, 253, 103},
  330. { 97, 253, 142},
  331. { 88, 253, 182},
  332. { 93, 253, 221},
  333. { 88, 254, 251},
  334. {177, 53, 34},
  335. {174, 54, 131},
  336. {172, 55, 172},
  337. {171, 57, 213},
  338. {170, 55, 249},
  339. {170, 57, 255},
  340. {165, 123, 37},
  341. {163, 123, 123},
  342. {162, 123, 164},
  343. {161, 122, 205},
  344. {161, 121, 241},
  345. {161, 121, 255},
  346. {158, 159, 33},
  347. {157, 158, 118},
  348. {157, 158, 159},
  349. {155, 157, 199},
  350. {155, 157, 239},
  351. {154, 156, 255},
  352. {152, 193, 40},
  353. {151, 193, 113},
  354. {150, 193, 153},
  355. {150, 192, 193},
  356. {148, 192, 232},
  357. {149, 191, 253},
  358. {146, 227, 28},
  359. {144, 227, 108},
  360. {144, 227, 147},
  361. {144, 227, 187},
  362. {142, 226, 227},
  363. {142, 225, 252},
  364. {138, 253, 36},
  365. {137, 253, 102},
  366. {136, 253, 141},
  367. {138, 254, 181},
  368. {135, 255, 220},
  369. {133, 255, 250},
  370. {214, 57, 30},
  371. {211, 59, 126},
  372. {209, 57, 168},
  373. {208, 55, 208},
  374. {207, 58, 247},
  375. {206, 61, 255},
  376. {204, 121, 32},
  377. {202, 121, 121},
  378. {201, 121, 161},
  379. {200, 120, 202},
  380. {200, 120, 241},
  381. {198, 119, 255},
  382. {198, 157, 37},
  383. {196, 157, 116},
  384. {195, 156, 157},
  385. {195, 156, 197},
  386. {194, 155, 236},
  387. {193, 155, 255},
  388. {191, 192, 36},
  389. {190, 191, 112},
  390. {189, 191, 152},
  391. {189, 191, 191},
  392. {188, 190, 230},
  393. {187, 190, 253},
  394. {185, 226, 28},
  395. {184, 226, 106},
  396. {183, 225, 146},
  397. {183, 225, 186},
  398. {182, 225, 225},
  399. {181, 224, 252},
  400. {178, 255, 35},
  401. {178, 255, 101},
  402. {177, 254, 141},
  403. {176, 254, 180},
  404. {176, 254, 220},
  405. {175, 253, 249},
  406. {247, 56, 30},
  407. {245, 57, 122},
  408. {243, 59, 163},
  409. {244, 60, 204},
  410. {242, 59, 241},
  411. {240, 55, 255},
  412. {241, 119, 36},
  413. {240, 120, 118},
  414. {238, 119, 158},
  415. {237, 119, 199},
  416. {237, 118, 238},
  417. {236, 118, 255},
  418. {235, 154, 36},
  419. {235, 154, 114},
  420. {234, 154, 154},
  421. {232, 154, 194},
  422. {232, 153, 234},
  423. {232, 153, 255},
  424. {230, 190, 30},
  425. {229, 189, 110},
  426. {228, 189, 150},
  427. {227, 189, 190},
  428. {227, 189, 229},
  429. {226, 188, 255},
  430. {224, 224, 35},
  431. {223, 224, 105},
  432. {222, 224, 144},
  433. {222, 223, 184},
  434. {222, 223, 224},
  435. {220, 223, 253},
  436. {217, 253, 28},
  437. {217, 253, 99},
  438. {216, 252, 139},
  439. {216, 252, 179},
  440. {215, 252, 218},
  441. {215, 251, 250},
  442. {255, 61, 30},
  443. {255, 60, 118},
  444. {255, 58, 159},
  445. {255, 56, 199},
  446. {255, 55, 238},
  447. {255, 59, 255},
  448. {255, 117, 29},
  449. {255, 117, 115},
  450. {255, 117, 155},
  451. {255, 117, 195},
  452. {255, 116, 235},
  453. {254, 116, 255},
  454. {255, 152, 27},
  455. {255, 152, 111},
  456. {254, 152, 152},
  457. {255, 152, 192},
  458. {254, 151, 231},
  459. {253, 151, 253},
  460. {255, 187, 33},
  461. {253, 187, 107},
  462. {252, 187, 148},
  463. {253, 187, 187},
  464. {254, 187, 227},
  465. {252, 186, 252},
  466. {252, 222, 34},
  467. {251, 222, 103},
  468. {251, 222, 143},
  469. {250, 222, 182},
  470. {251, 221, 222},
  471. {252, 221, 252},
  472. {251, 252, 15},
  473. {251, 252, 97},
  474. {249, 252, 137},
  475. {247, 252, 177},
  476. {247, 253, 217},
  477. {254, 255, 255},
  478. // Grayscale
  479. { 52, 53, 53},
  480. { 57, 58, 59},
  481. { 66, 67, 67},
  482. { 75, 76, 76},
  483. { 83, 85, 85},
  484. { 92, 93, 94},
  485. {101, 102, 102},
  486. {109, 111, 111},
  487. {118, 119, 119},
  488. {126, 127, 128},
  489. {134, 136, 136},
  490. {143, 144, 145},
  491. {151, 152, 153},
  492. {159, 161, 161},
  493. {167, 169, 169},
  494. {176, 177, 177},
  495. {184, 185, 186},
  496. {192, 193, 194},
  497. {200, 201, 202},
  498. {208, 209, 210},
  499. {216, 218, 218},
  500. {224, 226, 226},
  501. {232, 234, 234},
  502. {240, 242, 242},
  503. };
  504. for (size_t i = 0; i < sizeof(rgbColors) / sizeof(rgbColors[0]); ++i) {
  505. [m_colors addObject:DDMakeColor(rgbColors[i].r, rgbColors[i].g, rgbColors[i].b)];
  506. }
  507. // Color codes
  508. int index = 16;
  509. while (index < 256) {
  510. [m_codes_fg addObject:[NSString stringWithFormat:@"38;5;%dm", index]];
  511. [m_codes_bg addObject:[NSString stringWithFormat:@"48;5;%dm", index]];
  512. index++;
  513. }
  514. #else /* if MAP_TO_TERMINAL_APP_COLORS */
  515. // Standard xterm colors:
  516. //
  517. // These are the colors xterm shells use in xterm-256color mode.
  518. // In this mode, the shell supports 256 different colors, specified by 256 color codes.
  519. //
  520. // The first 16 color codes map to the original 16 color codes supported by the earlier xterm-color mode.
  521. // These are generally configurable, and thus we ignore them for the purposes of mapping,
  522. // as we can't rely on them being constant. They are largely duplicated anyway.
  523. //
  524. // The next 216 color codes are designed to run the spectrum, with several shades of every color.
  525. // The last 24 color codes represent a grayscale.
  526. //
  527. // While the color codes are standardized, the actual RGB values for each color code is not.
  528. // However most standard xterms follow a well known color chart,
  529. // which can easily be calculated using the simple formula below.
  530. //
  531. // More information about ansi escape codes can be found online.
  532. // http://en.wikipedia.org/wiki/ANSI_escape_code
  533. int index = 16;
  534. int r; // red
  535. int g; // green
  536. int b; // blue
  537. int ri; // r increment
  538. int gi; // g increment
  539. int bi; // b increment
  540. // Calculate xterm colors (using standard algorithm)
  541. int r = 0;
  542. int g = 0;
  543. int b = 0;
  544. for (ri = 0; ri < 6; ri++) {
  545. r = (ri == 0) ? 0 : 95 + (40 * (ri - 1));
  546. for (gi = 0; gi < 6; gi++) {
  547. g = (gi == 0) ? 0 : 95 + (40 * (gi - 1));
  548. for (bi = 0; bi < 6; bi++) {
  549. b = (bi == 0) ? 0 : 95 + (40 * (bi - 1));
  550. [m_codes_fg addObject:[NSString stringWithFormat:@"38;5;%dm", index]];
  551. [m_codes_bg addObject:[NSString stringWithFormat:@"48;5;%dm", index]];
  552. [m_colors addObject:DDMakeColor(r, g, b)];
  553. index++;
  554. }
  555. }
  556. }
  557. // Calculate xterm grayscale (using standard algorithm)
  558. r = 8;
  559. g = 8;
  560. b = 8;
  561. while (index < 256) {
  562. [m_codes_fg addObject:[NSString stringWithFormat:@"38;5;%dm", index]];
  563. [m_codes_bg addObject:[NSString stringWithFormat:@"48;5;%dm", index]];
  564. [m_colors addObject:DDMakeColor(r, g, b)];
  565. r += 10;
  566. g += 10;
  567. b += 10;
  568. index++;
  569. }
  570. #endif /* if MAP_TO_TERMINAL_APP_COLORS */
  571. codes_fg = [m_codes_fg copy];
  572. codes_bg = [m_codes_bg copy];
  573. colors = [m_colors copy];
  574. NSAssert([codes_fg count] == [codes_bg count], @"Invalid colors/codes array(s)");
  575. NSAssert([codes_fg count] == [colors count], @"Invalid colors/codes array(s)");
  576. }
  577. + (void)getRed:(CGFloat *)rPtr green:(CGFloat *)gPtr blue:(CGFloat *)bPtr fromColor:(DDColor *)color {
  578. #if TARGET_OS_IPHONE
  579. // iOS
  580. BOOL done = NO;
  581. if ([color respondsToSelector:@selector(getRed:green:blue:alpha:)]) {
  582. done = [color getRed:rPtr green:gPtr blue:bPtr alpha:NULL];
  583. }
  584. if (!done) {
  585. // The method getRed:green:blue:alpha: was only available starting iOS 5.
  586. // So in iOS 4 and earlier, we have to jump through hoops.
  587. CGColorSpaceRef rgbColorSpace = CGColorSpaceCreateDeviceRGB();
  588. unsigned char pixel[4];
  589. CGContextRef context = CGBitmapContextCreate(&pixel, 1, 1, 8, 4, rgbColorSpace, (CGBitmapInfo)(kCGBitmapAlphaInfoMask & kCGImageAlphaNoneSkipLast));
  590. CGContextSetFillColorWithColor(context, [color CGColor]);
  591. CGContextFillRect(context, CGRectMake(0, 0, 1, 1));
  592. if (rPtr) {
  593. *rPtr = pixel[0] / 255.0f;
  594. }
  595. if (gPtr) {
  596. *gPtr = pixel[1] / 255.0f;
  597. }
  598. if (bPtr) {
  599. *bPtr = pixel[2] / 255.0f;
  600. }
  601. CGContextRelease(context);
  602. CGColorSpaceRelease(rgbColorSpace);
  603. }
  604. #elif defined(DD_CLI) || !__has_include(<AppKit/NSColor.h>)
  605. // OS X without AppKit
  606. [color getRed:rPtr green:gPtr blue:bPtr alpha:NULL];
  607. #else /* if TARGET_OS_IPHONE */
  608. // OS X with AppKit
  609. NSColor *safeColor = [color colorUsingColorSpaceName:NSCalibratedRGBColorSpace];
  610. [safeColor getRed:rPtr green:gPtr blue:bPtr alpha:NULL];
  611. #endif /* if TARGET_OS_IPHONE */
  612. }
  613. /**
  614. * Maps the given color to the closest available color supported by the shell.
  615. * The shell may support 256 colors, or only 16.
  616. *
  617. * This method loops through the known supported color set, and calculates the closest color.
  618. * The array index of that color, within the colors array, is then returned.
  619. * This array index may also be used as the index within the codes_fg and codes_bg arrays.
  620. **/
  621. + (NSUInteger)codeIndexForColor:(DDColor *)inColor {
  622. CGFloat inR, inG, inB;
  623. [self getRed:&inR green:&inG blue:&inB fromColor:inColor];
  624. NSUInteger bestIndex = 0;
  625. CGFloat lowestDistance = 100.0f;
  626. NSUInteger i = 0;
  627. for (DDColor *color in colors) {
  628. // Calculate Euclidean distance (lower value means closer to given color)
  629. CGFloat r, g, b;
  630. [self getRed:&r green:&g blue:&b fromColor:color];
  631. #if CGFLOAT_IS_DOUBLE
  632. CGFloat distance = sqrt(pow(r - inR, 2.0) + pow(g - inG, 2.0) + pow(b - inB, 2.0));
  633. #else
  634. CGFloat distance = sqrtf(powf(r - inR, 2.0f) + powf(g - inG, 2.0f) + powf(b - inB, 2.0f));
  635. #endif
  636. NSLogVerbose(@"DDTTYLogger: %3lu : %.3f,%.3f,%.3f & %.3f,%.3f,%.3f = %.6f",
  637. (unsigned long)i, inR, inG, inB, r, g, b, distance);
  638. if (distance < lowestDistance) {
  639. bestIndex = i;
  640. lowestDistance = distance;
  641. NSLogVerbose(@"DDTTYLogger: New best index = %lu", (unsigned long)bestIndex);
  642. }
  643. i++;
  644. }
  645. return bestIndex;
  646. }
  647. + (instancetype)sharedInstance {
  648. static dispatch_once_t DDTTYLoggerOnceToken;
  649. dispatch_once(&DDTTYLoggerOnceToken, ^{
  650. // Xcode does NOT natively support colors in the Xcode debugging console.
  651. // You'll need to install the XcodeColors plugin to see colors in the Xcode console.
  652. //
  653. // PS - Please read the header file before diving into the source code.
  654. char *xcode_colors = getenv("XcodeColors");
  655. char *term = getenv("TERM");
  656. if (xcode_colors && (strcmp(xcode_colors, "YES") == 0)) {
  657. isaXcodeColorTTY = YES;
  658. } else if (term) {
  659. if (strcasestr(term, "color") != NULL) {
  660. isaColorTTY = YES;
  661. isaColor256TTY = (strcasestr(term, "256") != NULL);
  662. if (isaColor256TTY) {
  663. [self initialize_colors_256];
  664. } else {
  665. [self initialize_colors_16];
  666. }
  667. }
  668. }
  669. NSLogInfo(@"DDTTYLogger: isaColorTTY = %@", (isaColorTTY ? @"YES" : @"NO"));
  670. NSLogInfo(@"DDTTYLogger: isaColor256TTY: %@", (isaColor256TTY ? @"YES" : @"NO"));
  671. NSLogInfo(@"DDTTYLogger: isaXcodeColorTTY: %@", (isaXcodeColorTTY ? @"YES" : @"NO"));
  672. sharedInstance = [[[self class] alloc] init];
  673. });
  674. return sharedInstance;
  675. }
  676. - (instancetype)init {
  677. if (sharedInstance != nil) {
  678. return nil;
  679. }
  680. if ((self = [super init])) {
  681. // Initialize 'app' variable (char *)
  682. _appName = [[NSProcessInfo processInfo] processName];
  683. _appLen = [_appName lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
  684. if (_appLen == 0) {
  685. _appName = @"<UnnamedApp>";
  686. _appLen = [_appName lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
  687. }
  688. _app = (char *)malloc(_appLen + 1);
  689. if (_app == NULL) {
  690. return nil;
  691. }
  692. BOOL processedAppName = [_appName getCString:_app maxLength:(_appLen + 1) encoding:NSUTF8StringEncoding];
  693. if (NO == processedAppName) {
  694. free(_app);
  695. return nil;
  696. }
  697. // Initialize 'pid' variable (char *)
  698. _processID = [NSString stringWithFormat:@"%i", (int)getpid()];
  699. _pidLen = [_processID lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
  700. _pid = (char *)malloc(_pidLen + 1);
  701. if (_pid == NULL) {
  702. free(_app);
  703. return nil;
  704. }
  705. BOOL processedID = [_processID getCString:_pid maxLength:(_pidLen + 1) encoding:NSUTF8StringEncoding];
  706. if (NO == processedID) {
  707. free(_app);
  708. free(_pid);
  709. return nil;
  710. }
  711. // Initialize color stuff
  712. _colorsEnabled = NO;
  713. _colorProfilesArray = [[NSMutableArray alloc] initWithCapacity:8];
  714. _colorProfilesDict = [[NSMutableDictionary alloc] initWithCapacity:8];
  715. _automaticallyAppendNewlineForCustomFormatters = YES;
  716. }
  717. return self;
  718. }
  719. - (void)loadDefaultColorProfiles {
  720. [self setForegroundColor:DDMakeColor(214, 57, 30) backgroundColor:nil forFlag:DDLogFlagError];
  721. [self setForegroundColor:DDMakeColor(204, 121, 32) backgroundColor:nil forFlag:DDLogFlagWarning];
  722. }
  723. - (BOOL)colorsEnabled {
  724. // The design of this method is taken from the DDAbstractLogger implementation.
  725. // For extensive documentation please refer to the DDAbstractLogger implementation.
  726. // Note: The internal implementation MUST access the colorsEnabled variable directly,
  727. // This method is designed explicitly for external access.
  728. //
  729. // Using "self." syntax to go through this method will cause immediate deadlock.
  730. // This is the intended result. Fix it by accessing the ivar directly.
  731. // Great strides have been take to ensure this is safe to do. Plus it's MUCH faster.
  732. NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure");
  733. NSAssert(![self isOnInternalLoggerQueue], @"MUST access ivar directly, NOT via self.* syntax.");
  734. dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue];
  735. __block BOOL result;
  736. dispatch_sync(globalLoggingQueue, ^{
  737. dispatch_sync(self.loggerQueue, ^{
  738. result = self->_colorsEnabled;
  739. });
  740. });
  741. return result;
  742. }
  743. - (void)setColorsEnabled:(BOOL)newColorsEnabled {
  744. dispatch_block_t block = ^{
  745. @autoreleasepool {
  746. self->_colorsEnabled = newColorsEnabled;
  747. if ([self->_colorProfilesArray count] == 0) {
  748. [self loadDefaultColorProfiles];
  749. }
  750. }
  751. };
  752. // The design of this method is taken from the DDAbstractLogger implementation.
  753. // For extensive documentation please refer to the DDAbstractLogger implementation.
  754. // Note: The internal implementation MUST access the colorsEnabled variable directly,
  755. // This method is designed explicitly for external access.
  756. //
  757. // Using "self." syntax to go through this method will cause immediate deadlock.
  758. // This is the intended result. Fix it by accessing the ivar directly.
  759. // Great strides have been take to ensure this is safe to do. Plus it's MUCH faster.
  760. NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure");
  761. NSAssert(![self isOnInternalLoggerQueue], @"MUST access ivar directly, NOT via self.* syntax.");
  762. dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue];
  763. dispatch_async(globalLoggingQueue, ^{
  764. dispatch_async(self.loggerQueue, block);
  765. });
  766. }
  767. - (void)setForegroundColor:(DDColor *)txtColor backgroundColor:(DDColor *)bgColor forFlag:(DDLogFlag)mask {
  768. [self setForegroundColor:txtColor backgroundColor:bgColor forFlag:mask context:LOG_CONTEXT_ALL];
  769. }
  770. - (void)setForegroundColor:(DDColor *)txtColor backgroundColor:(DDColor *)bgColor forFlag:(DDLogFlag)mask context:(NSInteger)ctxt {
  771. dispatch_block_t block = ^{
  772. @autoreleasepool {
  773. DDTTYLoggerColorProfile *newColorProfile =
  774. [[DDTTYLoggerColorProfile alloc] initWithForegroundColor:txtColor
  775. backgroundColor:bgColor
  776. flag:mask
  777. context:ctxt];
  778. NSLogInfo(@"DDTTYLogger: newColorProfile: %@", newColorProfile);
  779. NSUInteger i = 0;
  780. for (DDTTYLoggerColorProfile *colorProfile in self->_colorProfilesArray) {
  781. if ((colorProfile->mask == mask) && (colorProfile->context == ctxt)) {
  782. break;
  783. }
  784. i++;
  785. }
  786. if (i < [self->_colorProfilesArray count]) {
  787. self->_colorProfilesArray[i] = newColorProfile;
  788. } else {
  789. [self->_colorProfilesArray addObject:newColorProfile];
  790. }
  791. }
  792. };
  793. // The design of the setter logic below is taken from the DDAbstractLogger implementation.
  794. // For documentation please refer to the DDAbstractLogger implementation.
  795. if ([self isOnInternalLoggerQueue]) {
  796. block();
  797. } else {
  798. dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue];
  799. NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure");
  800. dispatch_async(globalLoggingQueue, ^{
  801. dispatch_async(self.loggerQueue, block);
  802. });
  803. }
  804. }
  805. - (void)setForegroundColor:(DDColor *)txtColor backgroundColor:(DDColor *)bgColor forTag:(id <NSCopying>)tag {
  806. NSAssert([(id < NSObject >) tag conformsToProtocol: @protocol(NSCopying)], @"Invalid tag");
  807. dispatch_block_t block = ^{
  808. @autoreleasepool {
  809. DDTTYLoggerColorProfile *newColorProfile =
  810. [[DDTTYLoggerColorProfile alloc] initWithForegroundColor:txtColor
  811. backgroundColor:bgColor
  812. flag:(DDLogFlag)0
  813. context:0];
  814. NSLogInfo(@"DDTTYLogger: newColorProfile: %@", newColorProfile);
  815. self->_colorProfilesDict[tag] = newColorProfile;
  816. }
  817. };
  818. // The design of the setter logic below is taken from the DDAbstractLogger implementation.
  819. // For documentation please refer to the DDAbstractLogger implementation.
  820. if ([self isOnInternalLoggerQueue]) {
  821. block();
  822. } else {
  823. dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue];
  824. NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure");
  825. dispatch_async(globalLoggingQueue, ^{
  826. dispatch_async(self.loggerQueue, block);
  827. });
  828. }
  829. }
  830. - (void)clearColorsForFlag:(DDLogFlag)mask {
  831. [self clearColorsForFlag:mask context:0];
  832. }
  833. - (void)clearColorsForFlag:(DDLogFlag)mask context:(NSInteger)context {
  834. dispatch_block_t block = ^{
  835. @autoreleasepool {
  836. NSUInteger i = 0;
  837. for (DDTTYLoggerColorProfile *colorProfile in self->_colorProfilesArray) {
  838. if ((colorProfile->mask == mask) && (colorProfile->context == context)) {
  839. break;
  840. }
  841. i++;
  842. }
  843. if (i < [self->_colorProfilesArray count]) {
  844. [self->_colorProfilesArray removeObjectAtIndex:i];
  845. }
  846. }
  847. };
  848. // The design of the setter logic below is taken from the DDAbstractLogger implementation.
  849. // For documentation please refer to the DDAbstractLogger implementation.
  850. if ([self isOnInternalLoggerQueue]) {
  851. block();
  852. } else {
  853. dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue];
  854. NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure");
  855. dispatch_async(globalLoggingQueue, ^{
  856. dispatch_async(self.loggerQueue, block);
  857. });
  858. }
  859. }
  860. - (void)clearColorsForTag:(id <NSCopying>)tag {
  861. NSAssert([(id < NSObject >) tag conformsToProtocol: @protocol(NSCopying)], @"Invalid tag");
  862. dispatch_block_t block = ^{
  863. @autoreleasepool {
  864. [self->_colorProfilesDict removeObjectForKey:tag];
  865. }
  866. };
  867. // The design of the setter logic below is taken from the DDAbstractLogger implementation.
  868. // For documentation please refer to the DDAbstractLogger implementation.
  869. if ([self isOnInternalLoggerQueue]) {
  870. block();
  871. } else {
  872. dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue];
  873. NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure");
  874. dispatch_async(globalLoggingQueue, ^{
  875. dispatch_async(self.loggerQueue, block);
  876. });
  877. }
  878. }
  879. - (void)clearColorsForAllFlags {
  880. dispatch_block_t block = ^{
  881. @autoreleasepool {
  882. [self->_colorProfilesArray removeAllObjects];
  883. }
  884. };
  885. // The design of the setter logic below is taken from the DDAbstractLogger implementation.
  886. // For documentation please refer to the DDAbstractLogger implementation.
  887. if ([self isOnInternalLoggerQueue]) {
  888. block();
  889. } else {
  890. dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue];
  891. NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure");
  892. dispatch_async(globalLoggingQueue, ^{
  893. dispatch_async(self.loggerQueue, block);
  894. });
  895. }
  896. }
  897. - (void)clearColorsForAllTags {
  898. dispatch_block_t block = ^{
  899. @autoreleasepool {
  900. [self->_colorProfilesDict removeAllObjects];
  901. }
  902. };
  903. // The design of the setter logic below is taken from the DDAbstractLogger implementation.
  904. // For documentation please refer to the DDAbstractLogger implementation.
  905. if ([self isOnInternalLoggerQueue]) {
  906. block();
  907. } else {
  908. dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue];
  909. NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure");
  910. dispatch_async(globalLoggingQueue, ^{
  911. dispatch_async(self.loggerQueue, block);
  912. });
  913. }
  914. }
  915. - (void)clearAllColors {
  916. dispatch_block_t block = ^{
  917. @autoreleasepool {
  918. [self->_colorProfilesArray removeAllObjects];
  919. [self->_colorProfilesDict removeAllObjects];
  920. }
  921. };
  922. // The design of the setter logic below is taken from the DDAbstractLogger implementation.
  923. // For documentation please refer to the DDAbstractLogger implementation.
  924. if ([self isOnInternalLoggerQueue]) {
  925. block();
  926. } else {
  927. dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue];
  928. NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure");
  929. dispatch_async(globalLoggingQueue, ^{
  930. dispatch_async(self.loggerQueue, block);
  931. });
  932. }
  933. }
  934. - (void)logMessage:(DDLogMessage *)logMessage {
  935. NSString *logMsg = logMessage->_message;
  936. BOOL isFormatted = NO;
  937. if (_logFormatter) {
  938. logMsg = [_logFormatter formatLogMessage:logMessage];
  939. isFormatted = logMsg != logMessage->_message;
  940. }
  941. if (logMsg) {
  942. // Search for a color profile associated with the log message
  943. DDTTYLoggerColorProfile *colorProfile = nil;
  944. if (_colorsEnabled) {
  945. if (logMessage->_tag) {
  946. colorProfile = _colorProfilesDict[logMessage->_tag];
  947. }
  948. if (colorProfile == nil) {
  949. for (DDTTYLoggerColorProfile *cp in _colorProfilesArray) {
  950. if (logMessage->_flag & cp->mask) {
  951. // Color profile set for this context?
  952. if (logMessage->_context == cp->context) {
  953. colorProfile = cp;
  954. // Stop searching
  955. break;
  956. }
  957. // Check if LOG_CONTEXT_ALL was specified as a default color for this flag
  958. if (cp->context == LOG_CONTEXT_ALL) {
  959. colorProfile = cp;
  960. // We don't break to keep searching for more specific color profiles for the context
  961. }
  962. }
  963. }
  964. }
  965. }
  966. // Convert log message to C string.
  967. //
  968. // We use the stack instead of the heap for speed if possible.
  969. // But we're extra cautious to avoid a stack overflow.
  970. NSUInteger msgLen = [logMsg lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
  971. const BOOL useStack = msgLen < (1024 * 4);
  972. char msgStack[useStack ? (msgLen + 1) : 1]; // Analyzer doesn't like zero-size array, hence the 1
  973. char *msg = useStack ? msgStack : (char *)malloc(msgLen + 1);
  974. if (msg == NULL) {
  975. return;
  976. }
  977. BOOL logMsgEnc = [logMsg getCString:msg maxLength:(msgLen + 1) encoding:NSUTF8StringEncoding];
  978. if (!logMsgEnc) {
  979. if (!useStack && msg != NULL) {
  980. free(msg);
  981. }
  982. return;
  983. }
  984. // Write the log message to STDERR
  985. if (isFormatted) {
  986. // The log message has already been formatted.
  987. int iovec_len = (_automaticallyAppendNewlineForCustomFormatters) ? 5 : 4;
  988. struct iovec v[iovec_len];
  989. if (colorProfile) {
  990. v[0].iov_base = colorProfile->fgCode;
  991. v[0].iov_len = colorProfile->fgCodeLen;
  992. v[1].iov_base = colorProfile->bgCode;
  993. v[1].iov_len = colorProfile->bgCodeLen;
  994. v[iovec_len - 1].iov_base = colorProfile->resetCode;
  995. v[iovec_len - 1].iov_len = colorProfile->resetCodeLen;
  996. } else {
  997. v[0].iov_base = "";
  998. v[0].iov_len = 0;
  999. v[1].iov_base = "";
  1000. v[1].iov_len = 0;
  1001. v[iovec_len - 1].iov_base = "";
  1002. v[iovec_len - 1].iov_len = 0;
  1003. }
  1004. v[2].iov_base = (char *)msg;
  1005. v[2].iov_len = msgLen;
  1006. if (iovec_len == 5) {
  1007. v[3].iov_base = "\n";
  1008. v[3].iov_len = (msg[msgLen] == '\n') ? 0 : 1;
  1009. }
  1010. writev(STDERR_FILENO, v, iovec_len);
  1011. } else {
  1012. // The log message is unformatted, so apply standard NSLog style formatting.
  1013. int len;
  1014. char ts[24] = "";
  1015. size_t tsLen = 0;
  1016. // Calculate timestamp.
  1017. // The technique below is faster than using NSDateFormatter.
  1018. if (logMessage->_timestamp) {
  1019. NSTimeInterval epoch = [logMessage->_timestamp timeIntervalSince1970];
  1020. struct tm tm;
  1021. time_t time = (time_t)epoch;
  1022. (void)localtime_r(&time, &tm);
  1023. int milliseconds = (int)((epoch - floor(epoch)) * 1000.0);
  1024. len = snprintf(ts, 24, "%04d-%02d-%02d %02d:%02d:%02d:%03d", // yyyy-MM-dd HH:mm:ss:SSS
  1025. tm.tm_year + 1900,
  1026. tm.tm_mon + 1,
  1027. tm.tm_mday,
  1028. tm.tm_hour,
  1029. tm.tm_min,
  1030. tm.tm_sec, milliseconds);
  1031. tsLen = (NSUInteger)MAX(MIN(24 - 1, len), 0);
  1032. }
  1033. // Calculate thread ID
  1034. //
  1035. // How many characters do we need for the thread id?
  1036. // logMessage->machThreadID is of type mach_port_t, which is an unsigned int.
  1037. //
  1038. // 1 hex char = 4 bits
  1039. // 8 hex chars for 32 bit, plus ending '\0' = 9
  1040. char tid[9];
  1041. len = snprintf(tid, 9, "%s", [logMessage->_threadID cStringUsingEncoding:NSUTF8StringEncoding]);
  1042. size_t tidLen = (NSUInteger)MAX(MIN(9 - 1, len), 0);
  1043. // Here is our format: "%s %s[%i:%s] %s", timestamp, appName, processID, threadID, logMsg
  1044. struct iovec v[13];
  1045. if (colorProfile) {
  1046. v[0].iov_base = colorProfile->fgCode;
  1047. v[0].iov_len = colorProfile->fgCodeLen;
  1048. v[1].iov_base = colorProfile->bgCode;
  1049. v[1].iov_len = colorProfile->bgCodeLen;
  1050. v[12].iov_base = colorProfile->resetCode;
  1051. v[12].iov_len = colorProfile->resetCodeLen;
  1052. } else {
  1053. v[0].iov_base = "";
  1054. v[0].iov_len = 0;
  1055. v[1].iov_base = "";
  1056. v[1].iov_len = 0;
  1057. v[12].iov_base = "";
  1058. v[12].iov_len = 0;
  1059. }
  1060. v[2].iov_base = ts;
  1061. v[2].iov_len = tsLen;
  1062. v[3].iov_base = " ";
  1063. v[3].iov_len = 1;
  1064. v[4].iov_base = _app;
  1065. v[4].iov_len = _appLen;
  1066. v[5].iov_base = "[";
  1067. v[5].iov_len = 1;
  1068. v[6].iov_base = _pid;
  1069. v[6].iov_len = _pidLen;
  1070. v[7].iov_base = ":";
  1071. v[7].iov_len = 1;
  1072. v[8].iov_base = tid;
  1073. v[8].iov_len = MIN((size_t)8, tidLen); // snprintf doesn't return what you might think
  1074. v[9].iov_base = "] ";
  1075. v[9].iov_len = 2;
  1076. v[10].iov_base = (char *)msg;
  1077. v[10].iov_len = msgLen;
  1078. v[11].iov_base = "\n";
  1079. v[11].iov_len = (msg[msgLen] == '\n') ? 0 : 1;
  1080. writev(STDERR_FILENO, v, 13);
  1081. }
  1082. if (!useStack) {
  1083. free(msg);
  1084. }
  1085. }
  1086. }
  1087. - (DDLoggerName)loggerName {
  1088. return DDLoggerNameTTY;
  1089. }
  1090. @end
  1091. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  1092. @implementation DDTTYLoggerColorProfile
  1093. - (instancetype)initWithForegroundColor:(DDColor *)fgColor backgroundColor:(DDColor *)bgColor flag:(DDLogFlag)aMask context:(NSInteger)ctxt {
  1094. if ((self = [super init])) {
  1095. mask = aMask;
  1096. context = ctxt;
  1097. CGFloat r, g, b;
  1098. if (fgColor) {
  1099. [DDTTYLogger getRed:&r green:&g blue:&b fromColor:fgColor];
  1100. fg_r = (uint8_t)(r * 255.0f);
  1101. fg_g = (uint8_t)(g * 255.0f);
  1102. fg_b = (uint8_t)(b * 255.0f);
  1103. }
  1104. if (bgColor) {
  1105. [DDTTYLogger getRed:&r green:&g blue:&b fromColor:bgColor];
  1106. bg_r = (uint8_t)(r * 255.0f);
  1107. bg_g = (uint8_t)(g * 255.0f);
  1108. bg_b = (uint8_t)(b * 255.0f);
  1109. }
  1110. if (fgColor && isaColorTTY) {
  1111. // Map foreground color to closest available shell color
  1112. fgCodeIndex = [DDTTYLogger codeIndexForColor:fgColor];
  1113. fgCodeRaw = codes_fg[fgCodeIndex];
  1114. NSString *escapeSeq = @"\033[";
  1115. NSUInteger len1 = [escapeSeq lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
  1116. NSUInteger len2 = [fgCodeRaw lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
  1117. BOOL escapeSeqEnc = [escapeSeq getCString:(fgCode) maxLength:(len1 + 1) encoding:NSUTF8StringEncoding];
  1118. BOOL fgCodeRawEsc = [fgCodeRaw getCString:(fgCode + len1) maxLength:(len2 + 1) encoding:NSUTF8StringEncoding];
  1119. if (!escapeSeqEnc || !fgCodeRawEsc) {
  1120. return nil;
  1121. }
  1122. fgCodeLen = len1 + len2;
  1123. } else if (fgColor && isaXcodeColorTTY) {
  1124. // Convert foreground color to color code sequence
  1125. const char *escapeSeq = XCODE_COLORS_ESCAPE_SEQ;
  1126. int result = snprintf(fgCode, 24, "%sfg%u,%u,%u;", escapeSeq, fg_r, fg_g, fg_b);
  1127. fgCodeLen = (NSUInteger)MAX(MIN(result, (24 - 1)), 0);
  1128. } else {
  1129. // No foreground color or no color support
  1130. fgCode[0] = '\0';
  1131. fgCodeLen = 0;
  1132. }
  1133. if (bgColor && isaColorTTY) {
  1134. // Map background color to closest available shell color
  1135. bgCodeIndex = [DDTTYLogger codeIndexForColor:bgColor];
  1136. bgCodeRaw = codes_bg[bgCodeIndex];
  1137. NSString *escapeSeq = @"\033[";
  1138. NSUInteger len1 = [escapeSeq lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
  1139. NSUInteger len2 = [bgCodeRaw lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
  1140. BOOL escapeSeqEnc = [escapeSeq getCString:(bgCode) maxLength:(len1 + 1) encoding:NSUTF8StringEncoding];
  1141. BOOL bgCodeRawEsc = [bgCodeRaw getCString:(bgCode + len1) maxLength:(len2 + 1) encoding:NSUTF8StringEncoding];
  1142. if (!escapeSeqEnc || !bgCodeRawEsc) {
  1143. return nil;
  1144. }
  1145. bgCodeLen = len1 + len2;
  1146. } else if (bgColor && isaXcodeColorTTY) {
  1147. // Convert background color to color code sequence
  1148. const char *escapeSeq = XCODE_COLORS_ESCAPE_SEQ;
  1149. int result = snprintf(bgCode, 24, "%sbg%u,%u,%u;", escapeSeq, bg_r, bg_g, bg_b);
  1150. bgCodeLen = (NSUInteger)MAX(MIN(result, (24 - 1)), 0);
  1151. } else {
  1152. // No background color or no color support
  1153. bgCode[0] = '\0';
  1154. bgCodeLen = 0;
  1155. }
  1156. if (isaColorTTY) {
  1157. resetCodeLen = (NSUInteger)MAX(snprintf(resetCode, 8, "\033[0m"), 0);
  1158. } else if (isaXcodeColorTTY) {
  1159. resetCodeLen = (NSUInteger)MAX(snprintf(resetCode, 8, XCODE_COLORS_RESET), 0);
  1160. } else {
  1161. resetCode[0] = '\0';
  1162. resetCodeLen = 0;
  1163. }
  1164. }
  1165. return self;
  1166. }
  1167. - (NSString *)description {
  1168. return [NSString stringWithFormat:
  1169. @"<DDTTYLoggerColorProfile: %p mask:%i ctxt:%ld fg:%u,%u,%u bg:%u,%u,%u fgCode:%@ bgCode:%@>",
  1170. self, (int)mask, (long)context, fg_r, fg_g, fg_b, bg_r, bg_g, bg_b, fgCodeRaw, bgCodeRaw];
  1171. }
  1172. @end