Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(841)

Side by Side Diff: chrome/browser/extensions/activity_log.cc

Issue 11421192: Save extension activity log to a file. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src/
Patch Set: Created 8 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. 1 // Copyright (c) 2011 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #include "chrome/browser/extensions/activity_log.h" 5 #include "chrome/browser/extensions/activity_log.h"
6 6
7 #include <set>
7 #include "base/command_line.h" 8 #include "base/command_line.h"
9 #include "base/json/json_string_value_serializer.h"
8 #include "base/logging.h" 10 #include "base/logging.h"
9 #include "base/string_util.h" 11 #include "base/string_util.h"
12 #include "base/threading/thread_checker.h"
13 #include "chrome/browser/extensions/blocked_actions.h"
10 #include "chrome/browser/extensions/extension_service.h" 14 #include "chrome/browser/extensions/extension_service.h"
11 #include "chrome/browser/extensions/extension_system.h" 15 #include "chrome/browser/extensions/extension_system.h"
16 #include "chrome/browser/extensions/manager_actions.h"
12 #include "chrome/browser/profiles/profile.h" 17 #include "chrome/browser/profiles/profile.h"
18 #include "chrome/browser/profiles/profile_dependency_manager.h"
19 #include "chrome/browser/profiles/profile_keyed_service_factory.h"
20 #include "chrome/common/chrome_constants.h"
13 #include "chrome/common/chrome_switches.h" 21 #include "chrome/common/chrome_switches.h"
14 #include "chrome/common/extensions/extension.h" 22 #include "chrome/common/extensions/extension.h"
15 #include "content/public/browser/web_contents.h" 23 #include "content/public/browser/web_contents.h"
16 #include "googleurl/src/gurl.h" 24 #include "googleurl/src/gurl.h"
25 #include "sql/error_delegate_util.h"
26 #include "third_party/re2/re2/re2.h"
27
28 namespace {
29
30 static const char* kActivityThreadName = "Chrome_ActivityLogThread";
Eric Dingle 2012/12/13 20:37:34 No need for static keyword in this context.
felt 2012/12/15 02:51:52 Done.
31
32 // Concatenate an API call with its arguments.
33 std::string MakeCallSignature(const std::string& name, const ListValue& args) {
34 std::string call_signature = name + "(";
35 ListValue::const_iterator it = args.begin();
36 for (; it != args.end(); ++it) {
37 std::string arg;
38 JSONStringValueSerializer serializer(&arg);
39 if (serializer.SerializeAndOmitBinaryValues(**it)) {
40 if (it != args.begin())
41 call_signature += ", ";
42 call_signature += arg;
43 }
44 }
45 call_signature += ")";
46 return call_signature;
47 }
48
49 } // namespace
17 50
18 namespace extensions { 51 namespace extensions {
19 52
20 ActivityLog::ActivityLog() { 53 namespace {
54
55 // Each profile has different extensions, so we keep a different database for
56 // each profile.
57 class ActivityLogFactory : public ProfileKeyedServiceFactory {
Eric Dingle 2012/12/13 20:37:34 All classes should have at least one constructor a
felt 2012/12/15 02:51:52 Done.
58 public:
59 static ActivityLog* GetForProfile(Profile* profile) {
60 return static_cast<ActivityLog*>(
61 GetInstance()->GetServiceForProfile(profile, true));
62 }
63
64 static ActivityLogFactory* GetInstance();
65
66 private:
67 friend struct DefaultSingletonTraits<ActivityLogFactory>;
68
69 ActivityLogFactory()
70 : ProfileKeyedServiceFactory("ActivityLog",
71 ProfileDependencyManager::GetInstance()) { }
72
73 virtual ProfileKeyedService* BuildServiceInstanceFor(
74 Profile* profile) const OVERRIDE {
75 return new ActivityLog(profile);
76 }
77
78 virtual bool ServiceRedirectedInIncognito() const OVERRIDE {
79 return true;
80 }
81 };
82
83 ActivityLogFactory* ActivityLogFactory::GetInstance() {
84 return Singleton<ActivityLogFactory>::get();
85 }
86
87 // This handles errors from the database.
88 class KillActivityDatabaseErrorDelegate : public sql::ErrorDelegate {
89 public:
90 explicit KillActivityDatabaseErrorDelegate(ActivityLog* backend)
91 : backend_(backend),
92 scheduled_death_(false) { }
93
94 virtual int OnError(int error,
95 sql::Connection* connection,
96 sql::Statement* stmt) OVERRIDE {
97 if (!scheduled_death_ && sql::IsErrorCatastrophic(error)) {
98 scheduled_death_ = true;
99 backend_->KillActivityLogDatabase();
100 }
101 return error;
102 }
103
104 bool scheduled_death() const {
105 return scheduled_death_;
106 }
107
108 private:
109 ActivityLog* backend_;
110 bool scheduled_death_;
111
112 DISALLOW_COPY_AND_ASSIGN(KillActivityDatabaseErrorDelegate);
113 };
114
115 } // namespace
116
117 // Use GetInstance instead of directly creating an ActivityLog.
118 ActivityLog::ActivityLog(Profile* profile)
119 : thread_(new base::Thread(kActivityThreadName)) {
Eric Dingle 2012/12/13 20:37:34 I'm not saying that this is wrong, but you should
felt 2012/12/17 22:45:09 Now using the DB thread.
120
Eric Dingle 2012/12/13 20:37:34 Remove extra blank line.
felt 2012/12/15 02:51:52 Done.
121 log_activity_to_stdout_ = CommandLine::ForCurrentProcess()->
122 HasSwitch(switches::kEnableExtensionActivityLogging);
123
124 // If the database cannot be initialized, we keep chugging along and still
125 // log actions for displaying in the UI.
126 db_ = new ActivityDatabase();
Eric Dingle 2012/12/13 20:37:34 Would suggest using db_.reset(new ActivityDatabase
felt 2012/12/15 02:51:52 The docs in base/memory/ref_counted.h say to use a
127 KillActivityDatabaseErrorDelegate* error_delegate =
128 new KillActivityDatabaseErrorDelegate(this);
129 FilePath base_dir = profile->GetPath();
130 FilePath database_name = base_dir.Append(chrome::kActivityLogFilename);
131 sql::InitStatus status = db_->Init(database_name, error_delegate);
132 if (status == sql::INIT_OK) {
133 thread_->Start();
134 } else {
135 LOG(ERROR) << "Couldn't initialize the activity log database.";
136 if (error_delegate->scheduled_death()) {
137 KillActivityLogDatabase();
138 }
139 }
140 }
141
142 // If you call this constructor, none of the actions will be recorded in the
143 // database, but they will still be displayed in the UI. You should use
144 // GetInstance instead of directly creating an ActivityLog.
145 ActivityLog::ActivityLog()
146 : thread_(NULL) {
21 log_activity_to_stdout_ = CommandLine::ForCurrentProcess()-> 147 log_activity_to_stdout_ = CommandLine::ForCurrentProcess()->
22 HasSwitch(switches::kEnableExtensionActivityLogging); 148 HasSwitch(switches::kEnableExtensionActivityLogging);
23 } 149 }
24 150
25 ActivityLog::~ActivityLog() { 151 ActivityLog::~ActivityLog() {
152 if (thread_) {
153 // Delete and defensively NULL the thread.
154 base::Thread* thread = thread_;
155 thread_ = NULL;
156 delete thread;
157 }
26 } 158 }
27 159
28 // static 160 // static
29 ActivityLog* ActivityLog::GetInstance() { 161 ActivityLog* ActivityLog::GetInstance(Profile* profile) {
30 return Singleton<ActivityLog>::get(); 162 return ActivityLogFactory::GetForProfile(profile);
31 } 163 }
32 164
33 void ActivityLog::AddObserver(const Extension* extension, 165 void ActivityLog::AddObserver(const Extension* extension,
34 ActivityLog::Observer* observer) { 166 ActivityLog::Observer* observer) {
35 base::AutoLock scoped_lock(lock_); 167 base::AutoLock scoped_lock(lock_);
36 168
37 if (observers_.count(extension) == 0) { 169 if (observers_.count(extension) == 0) {
38 observers_[extension] = new ObserverListThreadSafe<Observer>; 170 observers_[extension] = new ObserverListThreadSafe<Observer>;
39 } 171 }
40 172
41 observers_[extension]->AddObserver(observer); 173 observers_[extension]->AddObserver(observer);
42 } 174 }
43 175
44 void ActivityLog::RemoveObserver(const Extension* extension, 176 void ActivityLog::RemoveObserver(const Extension* extension,
45 ActivityLog::Observer* observer) { 177 ActivityLog::Observer* observer) {
46 base::AutoLock scoped_lock(lock_); 178 base::AutoLock scoped_lock(lock_);
47 179
48 if (observers_.count(extension) == 1) { 180 if (observers_.count(extension) == 1) {
49 observers_[extension]->RemoveObserver(observer); 181 observers_[extension]->RemoveObserver(observer);
50 } 182 }
51 } 183 }
52 184
53 // Extension*
54 bool ActivityLog::HasObservers(const Extension* extension) const { 185 bool ActivityLog::HasObservers(const Extension* extension) const {
55 base::AutoLock scoped_lock(lock_); 186 base::AutoLock scoped_lock(lock_);
56 187
57 // We also return true if extension activity logging is enabled since in that 188 // We also return true if extension activity logging is enabled since in that
58 // case this class is observing all extensions. 189 // case this class is observing all extensions.
59 return observers_.count(extension) > 0 || log_activity_to_stdout_; 190 return observers_.count(extension) > 0 || log_activity_to_stdout_;
60 } 191 }
61 192
62 void ActivityLog::Log(const Extension* extension, 193 void ActivityLog::LogManagerAction(const Extension* extension,
63 Activity activity, 194 const std::string& name,
64 const std::string& message) const { 195 const ListValue& args) {
65 std::vector<std::string> messages(1, message); 196 base::AutoLock scoped_lock(lock_);
66 Log(extension, activity, messages); 197 std::string verb;
Eric Dingle 2012/12/13 20:37:34 std::string manager, verb;
felt 2012/12/15 02:51:52 Done.
67 } 198 std::string manager;
199 bool matches = RE2::FullMatch(name, "(.*?)\\.(.*)", &manager, &verb);
200 if (matches) {
201 std::string call_signature = MakeCallSignature(name, args);
202 scoped_refptr<ManagerAction> action = new ManagerAction(
203 extension->id(),
204 ManagerAction::StringAsActionType(verb),
205 ManagerAction::StringAsTargetType(manager),
206 call_signature,
207 base::Time::Now());
208 ScheduleAndForget(&ActivityDatabase::RecordManagerAction, action);
68 209
69 void ActivityLog::Log(const Extension* extension, 210 // Display the action.
70 Activity activity, 211 ObserverMap::const_iterator iter = observers_.find(extension);
71 const std::vector<std::string>& messages) const { 212 if (iter != observers_.end()) {
72 base::AutoLock scoped_lock(lock_); 213 iter->second->Notify(&Observer::OnExtensionActivity,
73 214 extension,
74 ObserverMap::const_iterator iter = observers_.find(extension); 215 ActivityLog::ACTIVITY_EXTENSION_API_CALL,
75 if (iter != observers_.end()) { 216 std::vector<std::string>(1, call_signature));
76 iter->second->Notify(&Observer::OnExtensionActivity, extension, activity, 217 }
77 messages); 218 if (log_activity_to_stdout_) {
78 } 219 LOG(INFO) << action->PrettyPrintForDebug();
79 220 }
80 if (log_activity_to_stdout_) { 221 } else {
81 LOG(INFO) << extension->id() << ":" << ActivityToString(activity) << ":" << 222 LOG(ERROR) << "Unknown API call! " << name;
82 JoinString(messages, ' ');
83 } 223 }
84 } 224 }
85 225
226 void ActivityLog::LogBlockedAction(const Extension* extension,
Eric Dingle 2012/12/13 20:37:34 You don't notify the observers on blocked calls?
felt 2012/12/15 02:51:52 Whoops. Done.
227 const std::string& blocked_name,
228 const ListValue& args,
229 const char* reason) {
230 base::AutoLock scoped_lock(lock_);
231 std::string blocked_call = MakeCallSignature(blocked_name, args);
232 scoped_refptr<BlockedAction> action = new BlockedAction(extension->id(),
233 blocked_call,
234 std::string(reason),
235 base::Time::Now());
236 ScheduleAndForget(&ActivityDatabase::RecordBlockedAction, action);
237 if (log_activity_to_stdout_) {
238 LOG(INFO) << action->PrettyPrintForDebug();
239 }
240 }
241
242 void ActivityLog::LogUrlAction(const Extension* extension,
243 const GURL& url,
244 const string16& url_title,
245 std::string& message,
246 const UrlAction::UrlActionType verb) {
247 base::AutoLock scoped_lock(lock_);
248 scoped_refptr<UrlAction> action = new UrlAction(
249 extension->id(),
250 verb,
251 url,
252 url_title,
253 message,
254 base::Time::Now());
255 ScheduleAndForget(&ActivityDatabase::RecordUrlAction, action);
256
257 // Display the action.
258 ObserverMap::const_iterator iter = observers_.find(extension);
259 if (iter != observers_.end()) {
260 iter->second->Notify(&Observer::OnExtensionActivity,
261 extension,
262 ActivityLog::ACTIVITY_CONTENT_SCRIPT,
Eric Dingle 2012/12/13 20:37:34 I assume that you'll notify for a different kind o
felt 2012/12/15 02:51:52 Yes, I'm going to change how this works as part of
263 std::vector<std::string>(1,
264 action->PrettyPrintForDebug()));
265 }
266 if (log_activity_to_stdout_) {
267 LOG(INFO) << action->PrettyPrintForDebug();
268 }
269 }
270
86 void ActivityLog::OnScriptsExecuted( 271 void ActivityLog::OnScriptsExecuted(
87 const content::WebContents* web_contents, 272 const content::WebContents* web_contents,
88 const ExecutingScriptsMap& extension_ids, 273 const ExecutingScriptsMap& extension_ids,
89 int32 on_page_id, 274 int32 on_page_id,
90 const GURL& on_url) { 275 const GURL& on_url) {
91 Profile* profile = 276 Profile* profile =
92 Profile::FromBrowserContext(web_contents->GetBrowserContext()); 277 Profile::FromBrowserContext(web_contents->GetBrowserContext());
93 const ExtensionService* extension_service = 278 const ExtensionService* extension_service =
94 ExtensionSystem::Get(profile)->extension_service(); 279 ExtensionSystem::Get(profile)->extension_service();
95 const ExtensionSet* extensions = extension_service->extensions(); 280 const ExtensionSet* extensions = extension_service->extensions();
96 281
97 for (ExecutingScriptsMap::const_iterator it = extension_ids.begin(); 282 for (ExecutingScriptsMap::const_iterator it = extension_ids.begin();
98 it != extension_ids.end(); ++it) { 283 it != extension_ids.end(); ++it) {
99 const Extension* extension = extensions->GetByID(it->first); 284 const Extension* extension = extensions->GetByID(it->first);
100 if (!extension || !HasObservers(extension)) 285 if (!extension || !HasObservers(extension))
101 continue; 286 continue;
102 287
103 for (std::set<std::string>::const_iterator it2 = it->second.begin(); 288 // If OnScriptsExecuted is fired because of tabs.executeScript, the list
104 it2 != it->second.end(); ++it2) { 289 // of content scripts will be empty. We don't want to log it because
105 std::vector<std::string> messages; 290 // the call to tabs.executeScript will have already been logged anyway.
106 messages.push_back(on_url.spec()); 291 if (!it->second.empty()) {
107 messages.push_back(*it2); 292 std::string ext_scripts_str = "";
108 Log(extension, ActivityLog::ACTIVITY_CONTENT_SCRIPT, messages); 293 for (std::set<std::string>::const_iterator it2 = it->second.begin();
294 it2 != it->second.end(); ++it2) {
295 ext_scripts_str += *it2;
296 ext_scripts_str += " ";
297 }
298 LogUrlAction(extension,
299 on_url,
300 web_contents->GetTitle(),
301 ext_scripts_str,
302 UrlAction::INSERTED);
109 } 303 }
110 } 304 }
111 } 305 }
112 306
307 void ActivityLog::KillActivityLogDatabase() {
308 if (db_.get()) {
309 ScheduleAndForget(&ActivityDatabase::KillDatabase);
310 }
311 }
312
113 // static 313 // static
114 const char* ActivityLog::ActivityToString(Activity activity) { 314 const char* ActivityLog::ActivityToString(Activity activity) {
115 switch (activity) { 315 switch (activity) {
116 case ActivityLog::ACTIVITY_EXTENSION_API_CALL: 316 case ActivityLog::ACTIVITY_EXTENSION_API_CALL:
117 return "api_call"; 317 return "api_call";
118 case ActivityLog::ACTIVITY_EXTENSION_API_BLOCK: 318 case ActivityLog::ACTIVITY_EXTENSION_API_BLOCK:
119 return "api_block"; 319 return "api_block";
120 case ActivityLog::ACTIVITY_CONTENT_SCRIPT: 320 case ActivityLog::ACTIVITY_CONTENT_SCRIPT:
121 return "content_script"; 321 return "content_script";
122 default: 322 default:
123 NOTREACHED(); 323 NOTREACHED();
124 return ""; 324 return "";
125 } 325 }
126 } 326 }
127 327
128 } // namespace extensions 328 } // namespace extensions
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698