Author: fdmanana Date: Tue May 31 20:32:56 2011 New Revision: 1129906 URL: http://svn.apache.org/viewvc?rev=1129906&view=rev Log: Improve error logging on replication write failures
If an error happens when writing a document to the target endpoint, log it's ID, revision, error and error reason. Also added a few tests to cover the cases where the target has validate_doc_update functions. Modified: couchdb/trunk/share/www/script/test/replication.js couchdb/trunk/src/couchdb/couch_api_wrap.erl couchdb/trunk/src/couchdb/couch_replicator_doc_copier.erl Modified: couchdb/trunk/share/www/script/test/replication.js URL: http://svn.apache.org/viewvc/couchdb/trunk/share/www/script/test/replication.js?rev=1129906&r1=1129905&r2=1129906&view=diff ============================================================================== --- couchdb/trunk/share/www/script/test/replication.js (original) +++ couchdb/trunk/share/www/script/test/replication.js Tue May 31 20:32:56 2011 @@ -504,6 +504,53 @@ couchTests.replication = function(debug) } + // test errors due to doc validate_doc_update functions in the target endpoint + docs = makeDocs(1, 8); + docs[2]["_attachments"] = { + "hello.txt": { + "content_type": "text/plain", + "data": "aGVsbG8gd29ybGQ=" // base64:encode("hello world") + } + }; + var ddoc = { + _id: "_design/test", + language: "javascript", + validate_doc_update: (function(newDoc, oldDoc, userCtx, secObj) { + if ((newDoc.integer % 2) !== 0) { + throw {forbidden: "I only like multiples of 2."}; + } + }).toString() + }; + + for (i = 0; i < dbPairs.length; i++) { + populateDb(sourceDb, docs); + populateDb(targetDb, [ddoc]); + + repResult = CouchDB.replicate( + dbPairs[i].source, + dbPairs[i].target + ); + TEquals(true, repResult.ok); + TEquals(7, repResult.history[0].missing_checked); + TEquals(7, repResult.history[0].missing_found); + TEquals(7, repResult.history[0].docs_read); + TEquals(3, repResult.history[0].docs_written); + TEquals(4, repResult.history[0].doc_write_failures); + + for (j = 0; j < docs.length; j++) { + doc = docs[j]; + copy = targetDb.open(doc._id); + + if (doc.integer % 2 === 0) { + T(copy !== null); + TEquals(copy.integer, doc.integer); + } else { + T(copy === null); + } + } + } + + // test create_target option docs = makeDocs(1, 2); Modified: couchdb/trunk/src/couchdb/couch_api_wrap.erl URL: http://svn.apache.org/viewvc/couchdb/trunk/src/couchdb/couch_api_wrap.erl?rev=1129906&r1=1129905&r2=1129906&view=diff ============================================================================== --- couchdb/trunk/src/couchdb/couch_api_wrap.erl (original) +++ couchdb/trunk/src/couchdb/couch_api_wrap.erl Tue May 31 20:32:56 2011 @@ -236,6 +236,8 @@ update_doc(#httpdb{} = HttpDb, #doc{id = case {Code, get_value(<<"error">>, Props)} of {401, <<"unauthorized">>} -> throw({unauthorized, get_value(<<"reason">>, Props)}); + {403, <<"forbidden">>} -> + throw({forbidden, get_value(<<"reason">>, Props)}); {412, <<"missing_stub">>} -> throw({missing_stub, get_value(<<"reason">>, Props)}); {_, Error} -> @@ -665,9 +667,10 @@ bulk_results_to_errors(Docs, {ok, Result lists:reverse(lists:foldl( fun({_, {ok, _}}, Acc) -> Acc; - ({#doc{id = Id}, Error}, Acc) -> - {_, Error, _Reason} = couch_httpd:error_info(Error), - [ {[{<<"id">>, Id}, {<<"error">>, Error}]} | Acc ] + ({#doc{id = Id, revs = {Pos, [RevId | _]}}, Error}, Acc) -> + {_, Error, Reason} = couch_httpd:error_info(Error), + [ {[{id, Id}, {rev, rev_to_str({Pos, RevId})}, + {error, Error}, {reason, Reason}]} | Acc ] end, [], lists:zip(Docs, Results))); @@ -676,9 +679,9 @@ bulk_results_to_errors(Docs, {ok, Result bulk_results_to_errors(_Docs, {aborted, Results}, interactive_edit) -> lists:map( - fun({{Id, _Rev}, Err}) -> - {_, Error, _Reason} = couch_httpd:error_info(Err), - {[{<<"id">>, Id}, {<<"error">>, Error}]} + fun({{Id, Rev}, Err}) -> + {_, Error, Reason} = couch_httpd:error_info(Err), + {[{id, Id}, {rev, rev_to_str(Rev)}, {error, Error}, {reason, Reason}]} end, Results); @@ -690,12 +693,21 @@ bulk_results_to_errors(_Docs, Results, r Acc; Error -> Id = get_value(<<"id">>, Props, get_value(id, Props)), - [ {[{<<"id">>, Id}, {<<"error">>, Error}]} | Acc ] + Rev = get_value(<<"rev">>, Props, get_value(rev, Props)), + Reason = get_value(<<"reason">>, Props, get_value(reason, Props)), + [ {[{id, Id}, {rev, rev_to_str(Rev)}, + {error, Error}, {reason, Reason}]} | Acc ] end end, [], Results)). +rev_to_str({_Pos, _Id} = Rev) -> + couch_doc:rev_to_str(Rev); +rev_to_str(Rev) -> + Rev. + + stream_doc({JsonBytes, Atts, Boundary, Len}) -> case erlang:erase({doc_streamer, Boundary}) of Pid when is_pid(Pid) -> Modified: couchdb/trunk/src/couchdb/couch_replicator_doc_copier.erl URL: http://svn.apache.org/viewvc/couchdb/trunk/src/couchdb/couch_replicator_doc_copier.erl?rev=1129906&r1=1129905&r2=1129906&view=diff ============================================================================== --- couchdb/trunk/src/couchdb/couch_replicator_doc_copier.erl (original) +++ couchdb/trunk/src/couchdb/couch_replicator_doc_copier.erl Tue May 31 20:32:56 2011 @@ -36,6 +36,12 @@ start_db_compaction_notifier/2, stop_db_compaction_notifier/1 ]). +-import(couch_util, [ + to_binary/1, + get_value/2, + get_value/3 +]). + -record(batch, { docs = [], @@ -490,15 +496,15 @@ flush_docs(Target, DocList) -> Target, DocList, [delay_commit], replicated_changes), DbUri = couch_api_wrap:db_uri(Target), lists:foreach( - fun({[ {<<"id">>, Id}, {<<"error">>, <<"unauthorized">>} ]}) -> - ?LOG_ERROR("Replicator: unauthorized to write document" - " `~s` to `~s`", [Id, DbUri]); - (_) -> - ok + fun({Props}) -> + ?LOG_ERROR("Replicator: couldn't write document `~s`, revision `~s`," + " to target database `~s`. Error: `~s`, reason: `~s`.", + [get_value(id, Props, ""), get_value(rev, Props, ""), DbUri, + get_value(error, Props, ""), get_value(reason, Props, "")]) end, Errors), {length(DocList) - length(Errors), length(Errors)}. -flush_doc(Target, #doc{id = Id} = Doc) -> +flush_doc(Target, #doc{id = Id, revs = {Pos, [RevId | _]}} = Doc) -> try couch_api_wrap:update_doc(Target, Doc, [], replicated_changes) of {ok, _} -> ok; @@ -507,8 +513,16 @@ flush_doc(Target, #doc{id = Id} = Doc) - [Id, couch_api_wrap:db_uri(Target), couch_util:to_binary(Error)]), Error catch - throw:{unauthorized, _} -> - ?LOG_ERROR("Replicator: unauthorized to write document `~s` to `~s`", - [Id, couch_api_wrap:db_uri(Target)]), - {error, unauthorized} + throw:{Error, Reason} -> + ?LOG_ERROR("Replicator: couldn't write document `~s`, revision `~s`," + " to target database `~s`. Error: `~s`, reason: `~s`.", + [Id, couch_doc:rev_to_str({Pos, RevId}), + couch_api_wrap:db_uri(Target), to_binary(Error), to_binary(Reason)]), + {error, Error}; + Tag:Err -> + ?LOG_ERROR("Replicator: couldn't write document `~s`, revision `~s`," + " to target database `~s`. Error: `~s`.", + [Id, couch_doc:rev_to_str({Pos, RevId}), + couch_api_wrap:db_uri(Target), to_binary({Tag, Err})]), + {error, Err} end.